#!/usr/bin/perl use strict; use warnings; use Carp qw/croak/; use Config qw/%Config/; use Cwd qw/cwd/; use File::Spec::Functions qw/catfile path/; use App::Rgit::Utils qw/:codes :levels/; use App::Rgit; our $VERSION; BEGIN { $VERSION = '0.05'; } my %opts; my $cmd; BEGIN { @ARGV = grep { defined $cmd ? $_ : ( /^-([DIKV]+)$/ ? do { $opts{$_} = 1 for split //, $1; () } : do { $cmd = $_ unless /^-/; $_ } ) } @ARGV; $cmd = ' ' unless defined $cmd; } my $shell; BEGIN { if (-t && $opts{I}) { if (eval "require Term::ReadKey; 1") { Term::ReadKey->import; *policy = \&policy_interactive; for (grep defined, $ENV{SHELL}, '/bin/sh') { if (-x $_) { $shell = $_; last; } } } else { warn "You have to install Term::ReadKey to use the interactive mode.\n"; } } *policy = $opts{K} ? \&policy_keep : \&policy_default unless defined *policy{CODE}; } setpgrp 0, 0 if $Config{d_setpgrp}; my $git = $ENV{GIT_EXEC_PATH}; unless (defined $git) { for (path) { my $g = catfile $_, 'git'; if (-x $g) { $git = $g; last; } } } croak "Couldn't find any valid git executable" unless defined $git; my $root = $ENV{GIT_DIR}; $root = cwd unless defined $root; my $ar = App::Rgit->new( git => $git, root => $root, cmd => $cmd, args => \@ARGV, policy => \&policy, debug => $opts{D} ? INFO : WARN, ); print STDOUT "rgit $VERSION\n" if $opts{V}; exit $ar->run; sub policy_default { my ($cmd, $conf, $repo, $status, $signal) = @_; return NEXT unless $status; return LAST; } sub policy_keep { NEXT } sub policy_interactive { my ($cmd, $conf, $repo, $status, $signal) = @_; return NEXT unless $status; my %codes = ( 'a' => [ LAST, 'aborting' ], 'i' => [ NEXT, 'ignoring' ], 'I' => [ NEXT | SAVE, 'ignoring all' ], 'r' => [ REDO, 'retrying' ], ); my $int = { GetControlChars() }->{INTERRUPT}; while (1) { $conf->warn("[a]bort, [i]gnore, [I]gnore all, [r]etry, open [s]hell ?"); ReadMode(4); my $key = ReadKey(0); ReadMode(1); print STDERR "\n"; next unless defined $key; if ($key eq $int) { $conf->warn("Interrupted, aborting\n"); return LAST; } elsif ($key eq 's') { if (defined $shell) { $conf->info('Opening shell in ', $repo->work, "\n"); my $cwd = cwd; $repo->chdir; system { $shell } $shell; chdir $cwd; } else { $conf->err("Couldn't find any shell\n"); } } elsif (exists $codes{$key}) { my $code = $codes{$key}; $conf->info('Okay, ', $code->[1], "\n"); return $code->[0]; } } } __END__ =head1 NAME rgit - Recursively execute a command on all the git repositories in a directory tree. =head1 VERSION Version 0.05 =head1 SYNOPSIS rgit [-K|-I|-D|-V] [GIT_OPTIONS] COMMAND [COMMAND_ARGS] =head1 DESCRIPTION This utility recursively searches in a root directory (which may be the current working directory or - if it has been set - the directory given by the C environment variable) for all git repositories, sort this list by the repository path, C into each of them, and executes the specified git command. Moreover, those formats are substituted in the arguments before running the command : =over 4 =item * C<%n> with the current repository name. =item * C<%g> with the relative path (based from the root directory) to the current repository. =item * C<%G> with the absolute path to the current repository. =item * C<%w> with the relative path (based from the root directory) to the current repository's working directory. =item * C<%W> with the absolute path to the current repository's working directory. =item * C<%b> with a "bareified" relative path, i.e. C<%g> if this is a bare repository, and C<%w.git> otherwise. =item * C<%B> with an absolute version of the "bareified" path. =item * C<%R> with the absolute path to the root directory. =item * C<%%> with a bare C<%>. =back There are actually a few commands that are only executed once in the root directory : C, C, C, C and C. For any of those, no format substitution is done. You can specify which C executable to use with the C environment variable. =head1 COMMAND LINE SWITCHES C takes its options as the capital switches that comes before the git command. It's possible to bundle them together. They are removed from the argument list before calling C. =over 4 =item * C<-K> Keep processing on error. The default policy is to stop whenever an error occured. =item * C<-I> Enables interactive mode when the standard input is a tty. Requires L to be installed. This lets you choose interactively what to do when one of the commands returns a non-zero status. =item * C<-D> Outputs diagnostics. =item * C<-V> Outputs the version. =back =head1 EXAMPLES Execute C on all the repositories below the current directory : rgit gc Tag all the repositories with their name : rgit tag %n Add a remote to all repositories in "/foo/bar" to their bare counterpart in C on F : GIT_DIR="/foo/bar" rgit remote add host git://host/qux/%b =head1 DEPENDENCIES The core modules L, L, L, L, L, L and L. L. =head1 AUTHOR Vincent Pit, C<< >>, L. You can contact me by mail or on C (vincent). =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc rgit Tests code coverage report is available at L. =head1 COPYRIGHT & LICENSE Copyright 2008 Vincent Pit, all rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut