| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package App::Multigit::Repo; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 1 |  |  | 1 |  | 501 | use App::Multigit::Loop qw(loop); | 
|  | 1 |  |  |  |  | 4 |  | 
|  | 1 |  |  |  |  | 69 |  | 
| 4 | 1 |  |  | 1 |  | 962 | use IO::Async::Process; | 
|  | 1 |  |  |  |  | 8053 |  | 
|  | 1 |  |  |  |  | 11 |  | 
| 5 | 1 |  |  | 1 |  | 29 | use Future; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 5 |  | 
| 6 | 1 |  |  | 1 |  | 884 | use Moo; | 
|  | 1 |  |  |  |  | 123075 |  | 
|  | 1 |  |  |  |  | 9 |  | 
| 7 | 1 |  |  | 1 |  | 1653 | use Cwd 'getcwd'; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 48 |  | 
| 8 | 1 |  |  | 1 |  | 5 | use Try::Tiny; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 54 |  | 
| 9 |  |  |  |  |  |  |  | 
| 10 | 1 |  |  | 1 |  | 28 | use 5.014; | 
|  | 1 |  |  |  |  | 4 |  | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | our $VERSION = '0.16'; | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | =encoding utf8 | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | =head1 NAME | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | App::Multigit::Repo - Moo class to represent a repo | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | Holds the name and config for a repo, to make future chaining code cleaner. | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | You can curry objects is what I mean. | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | =head1 PROPERTIES | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | =head2 name | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | Name as in the key from the mgconfig file that defines this repo. As in, the | 
| 31 |  |  |  |  |  |  | URL. | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | It's called name because it doesn't have to be the URL, but is by default. | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | =cut | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | has name => ( | 
| 38 |  |  |  |  |  |  | is => 'ro', | 
| 39 |  |  |  |  |  |  | ); | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | =head2 config | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | The config from the mgconfig file for this repo. | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | This is given a C key if the config does not already specify one. | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | =cut | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | has config => ( | 
| 50 |  |  |  |  |  |  | is => 'ro', | 
| 51 |  |  |  |  |  |  | ); | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | =head1 METHODS | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | =head2 run($command, [%data]) | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | Run a command, in one of two ways: | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | If the command is a CODE ref, it is run with this Repo object, and the entirety | 
| 60 |  |  |  |  |  |  | of C<%data>. The CODE reference should use normal print/say/warn/die behaviour. | 
| 61 |  |  |  |  |  |  | Its return value is discarded. If the subref returns at all, it is considered to | 
| 62 |  |  |  |  |  |  | have succeeded. | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | If it is an ARRAY ref, it is run with IO::Async::Process, with C sent | 
| 65 |  |  |  |  |  |  | to the process's STDIN. | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | A Future object is returned. When the command finishes, the Future is completed | 
| 68 |  |  |  |  |  |  | with a hash-shaped list identical to the one C accepts. | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | If an error occurs I running the command (i.e. if IO::Async throws the | 
| 71 |  |  |  |  |  |  | error), it will behave as though an error occurred within the command, and | 
| 72 |  |  |  |  |  |  | C will be set to 255. | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | =head3 data | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | C accepts a hash of data. If C or C are provided here, the | 
| 77 |  |  |  |  |  |  | Future will have these values in C and C, and | 
| 78 |  |  |  |  |  |  | C and C will get populated with the I STDOUT and STDERR | 
| 79 |  |  |  |  |  |  | from the provided C<$command>. | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | =over | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | =item C - The STDOUT from the operation. Will be set to the empty string | 
| 84 |  |  |  |  |  |  | if undef. | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | =item C - The STDERR from the operation. Will be set to the empty string | 
| 87 |  |  |  |  |  |  | if undef. | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | =item C - The C<$?> equivalent as produced by IO::Async::Process. | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | =item C - The STDOUT from the prior command | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | =item C - The STDERR from the prior command | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | =back | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | C and C are never used; they are provided for you to | 
| 98 |  |  |  |  |  |  | write any procedure you may require to concatenate new output with old. See | 
| 99 |  |  |  |  |  |  | C. | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | =head3 IO::Async::Process | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | The special key C to the C<%data> hash will be removed from the hash | 
| 104 |  |  |  |  |  |  | and used as configuration for the L object that powers the | 
| 105 |  |  |  |  |  |  | whole system. | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | It currently supports the C option, to prevent attempting to C | 
| 108 |  |  |  |  |  |  | into the repo's directory. | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  | $repo->run($subref, ia_config => { no_cd => 1 }); | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | =cut | 
| 113 |  |  |  |  |  |  |  | 
| 114 |  |  |  |  |  |  | sub run { | 
| 115 | 0 |  |  | 0 | 1 |  | my ($self, $command, %data) = @_; | 
| 116 | 0 |  |  |  |  |  | my $future = loop->new_future; | 
| 117 |  |  |  |  |  |  |  | 
| 118 | 0 |  |  |  |  |  | bless $future, 'App::Multigit::Future'; | 
| 119 |  |  |  |  |  |  |  | 
| 120 | 0 |  | 0 |  |  |  | $data{stdout} //= ''; | 
| 121 | 0 |  |  |  |  |  | my $ia_config = delete $data{ia_config}; | 
| 122 |  |  |  |  |  |  |  | 
| 123 | 0 |  |  |  |  |  | my $ignore_stdout = $App::Multigit::BEHAVIOUR{ignore_stdout}; | 
| 124 | 0 |  |  |  |  |  | my $ignore_stderr = $App::Multigit::BEHAVIOUR{ignore_stderr}; | 
| 125 |  |  |  |  |  |  |  | 
| 126 |  |  |  |  |  |  | my $finish_code = sub { | 
| 127 | 0 |  |  | 0 |  |  | my (undef, $exitcode, $stdout, $stderr) = @_; | 
| 128 |  |  |  |  |  |  | my %details = ( | 
| 129 |  |  |  |  |  |  | stdout => $ignore_stdout ? '' : $stdout, | 
| 130 |  |  |  |  |  |  | stderr => $ignore_stderr ? '' : $stderr, | 
| 131 |  |  |  |  |  |  | exitcode => $exitcode, | 
| 132 |  |  |  |  |  |  | past_stdout => $ignore_stdout ? '' : $data{stdout}, | 
| 133 |  |  |  |  |  |  | past_stderr => $ignore_stderr ? '' : $data{stderr}, | 
| 134 | 0 | 0 |  |  |  |  | ); | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 135 |  |  |  |  |  |  |  | 
| 136 | 0 | 0 |  |  |  |  | if ($exitcode == 0) { | 
| 137 | 0 |  |  |  |  |  | $future->done(%details); | 
| 138 |  |  |  |  |  |  | } | 
| 139 |  |  |  |  |  |  | else { | 
| 140 | 0 |  |  |  |  |  | $future->fail( | 
| 141 |  |  |  |  |  |  | "Child process exited with nonzero exit status", | 
| 142 |  |  |  |  |  |  | exit_nonzero => %details); | 
| 143 |  |  |  |  |  |  | } | 
| 144 | 0 |  |  |  |  |  | }; | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  | try | 
| 147 |  |  |  |  |  |  | { | 
| 148 | 0 |  |  | 0 |  |  | my $setup = []; | 
| 149 | 0 | 0 |  |  |  |  | unless($ia_config->{no_cd}) { | 
| 150 |  |  |  |  |  |  | $setup = [ | 
| 151 |  |  |  |  |  |  | chdir => $self->config->{dir} | 
| 152 | 0 |  |  |  |  |  | ]; | 
| 153 |  |  |  |  |  |  | } | 
| 154 | 0 | 0 |  |  |  |  | if (ref $command eq 'CODE') { | 
| 155 |  |  |  |  |  |  | loop->run_child( | 
| 156 |  |  |  |  |  |  | code => sub { | 
| 157 | 0 |  |  |  |  |  | $command->($self, %data); 0; | 
|  | 0 |  |  |  |  |  |  | 
| 158 |  |  |  |  |  |  | }, | 
| 159 | 0 |  |  |  |  |  | setup => $setup, | 
| 160 |  |  |  |  |  |  | on_finish => $finish_code, | 
| 161 |  |  |  |  |  |  | ); | 
| 162 |  |  |  |  |  |  | } | 
| 163 |  |  |  |  |  |  | else { | 
| 164 |  |  |  |  |  |  | loop->run_child( | 
| 165 |  |  |  |  |  |  | command => $command, | 
| 166 |  |  |  |  |  |  | setup => $setup, | 
| 167 |  |  |  |  |  |  | stdin => $data{stdout}, | 
| 168 | 0 |  |  |  |  |  | on_finish => $finish_code, | 
| 169 |  |  |  |  |  |  | ) | 
| 170 |  |  |  |  |  |  | } | 
| 171 |  |  |  |  |  |  | } | 
| 172 |  |  |  |  |  |  | catch | 
| 173 |  |  |  |  |  |  | { | 
| 174 |  |  |  |  |  |  | # make failures coming from the Async code come out as an error | 
| 175 |  |  |  |  |  |  | # relating to the repo as they probably are. | 
| 176 |  |  |  |  |  |  | # rather than crashing the whole program hard. | 
| 177 |  |  |  |  |  |  | # the common error case is the subdirectory for the module not existing. | 
| 178 | 0 |  |  | 0 |  |  | $finish_code->(undef, 255, '', "Error running\n" . $_); | 
| 179 | 0 |  |  |  |  |  | }; | 
| 180 | 0 |  |  |  |  |  | return $future; | 
| 181 |  |  |  |  |  |  | } | 
| 182 |  |  |  |  |  |  |  | 
| 183 |  |  |  |  |  |  | =head2 gather(%data) | 
| 184 |  |  |  |  |  |  |  | 
| 185 |  |  |  |  |  |  | Intended for currying. This goes between Cs and ensures output is not lost. | 
| 186 |  |  |  |  |  |  |  | 
| 187 |  |  |  |  |  |  | Concatenates the STDOUT and STDERR from the command with the respective STDOUT | 
| 188 |  |  |  |  |  |  | or STDERR of the previous command and continues the chain. | 
| 189 |  |  |  |  |  |  |  | 
| 190 |  |  |  |  |  |  | $repo->run([qw/git command/]) | 
| 191 |  |  |  |  |  |  | ->then($repo->curry::run([qw/another git command/])) | 
| 192 |  |  |  |  |  |  | ->then($repo->curry::gather) | 
| 193 |  |  |  |  |  |  | ->then(App::Multigit::report($repo)) | 
| 194 |  |  |  |  |  |  |  | 
| 195 |  |  |  |  |  |  | See C for the shape of the data | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | =cut | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | sub gather { | 
| 200 | 0 |  |  | 0 | 1 |  | my ($self, %data) = @_; | 
| 201 |  |  |  |  |  |  |  | 
| 202 | 1 |  |  | 1 |  | 5 | no warnings 'uninitialized'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 257 |  | 
| 203 | 0 |  |  |  |  |  | my $stdout = join "\n", grep { $_ } delete $data{past_stdout}, $data{stdout}; | 
|  | 0 |  |  |  |  |  |  | 
| 204 | 0 |  |  |  |  |  | my $stderr = join "\n", grep { $_ } delete $data{past_stderr}, $data{stderr}; | 
|  | 0 |  |  |  |  |  |  | 
| 205 | 0 | 0 |  |  |  |  | $data{stdout} = $stdout unless $App::Multigit::BEHAVIOUR{ignore_stdout}; | 
| 206 | 0 | 0 |  |  |  |  | $data{stderr} = $stderr unless $App::Multigit::BEHAVIOUR{ignore_stderr}; | 
| 207 |  |  |  |  |  |  |  | 
| 208 | 0 |  |  |  |  |  | Future->done(%data); | 
| 209 |  |  |  |  |  |  | } | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | =head2 report(%data) | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | Intended for currying, and accepts a hash-shaped list à la C. | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | Returns a Future that yields a two-element list of the directory - from the | 
| 216 |  |  |  |  |  |  | config - and the STDOUT from the command, indented with tabs. | 
| 217 |  |  |  |  |  |  |  | 
| 218 |  |  |  |  |  |  | Use C to collect STDOUT/STDERR from previous commands too. | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | The yielded list is intended for use as a hash constructor. | 
| 221 |  |  |  |  |  |  |  | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | my $future = App::Multigit::each(sub { | 
| 224 |  |  |  |  |  |  | my $repo = shift; | 
| 225 |  |  |  |  |  |  | $repo->run([qw/git command/]) | 
| 226 |  |  |  |  |  |  | ->then($repo->curry::run([qw/another git command/])) | 
| 227 |  |  |  |  |  |  | ->then($repo->curry::gather) | 
| 228 |  |  |  |  |  |  | ->then($repo->curry::report) | 
| 229 |  |  |  |  |  |  | ; | 
| 230 |  |  |  |  |  |  | }); | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  | my %report = $future->get; | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | for my $dir (sort keys %report) { ... } | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | =cut | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  | sub report { | 
| 239 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 240 | 0 |  |  |  |  |  | my %data = @_; | 
| 241 |  |  |  |  |  |  |  | 
| 242 | 0 |  |  |  |  |  | my $dir = $self->config->{dir}; | 
| 243 |  |  |  |  |  |  |  | 
| 244 | 0 | 0 |  |  |  |  | $data{stdout} = '' if $App::Multigit::BEHAVIOUR{ignore_stdout}; | 
| 245 | 0 | 0 |  |  |  |  | $data{stderr} = '' if $App::Multigit::BEHAVIOUR{ignore_stderr}; | 
| 246 |  |  |  |  |  |  |  | 
| 247 | 0 |  |  |  |  |  | my $output = do { | 
| 248 | 1 |  |  | 1 |  | 6 | no warnings 'uninitialized'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 195 |  | 
| 249 | 0 |  |  |  |  |  | _indent($data{stdout}, 1) . _indent($data{stderr}, 1); | 
| 250 |  |  |  |  |  |  | }; | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  | return Future->done unless $App::Multigit::BEHAVIOUR{report_on_no_output} | 
| 253 | 0 | 0 | 0 |  |  |  | or $output =~ s/\s//gr; | 
| 254 |  |  |  |  |  |  |  | 
| 255 | 0 |  |  |  |  |  | return Future->done( | 
| 256 |  |  |  |  |  |  | $dir => $output | 
| 257 |  |  |  |  |  |  | ); | 
| 258 |  |  |  |  |  |  | } | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | =head2 _indent | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | Returns a copy of the first argument indented by the number of tabs in the | 
| 264 |  |  |  |  |  |  | second argument. Not really a method on this class but it's here if you want it. | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  | =cut | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | sub _indent { | 
| 269 | 0 | 0 |  | 0 |  |  | return if not defined $_[0]; | 
| 270 | 0 |  |  |  |  |  | $_[0] =~ s/^/"\t" x $_[1]/germ | 
|  | 0 |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | } | 
| 272 |  |  |  |  |  |  | 1; | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | __END__ |