On Wed, Sep 05, 2007 at 07:31:44PM +0200, Matthieu Moy wrote:
I tend to have a lot of small projects, so I have on the order of 80 git
repositories on each machine I use, most of which have a 'mothership'
origin on a central, backed-up machine.
When I sit down to work, I want to see which repositories
have changes that need to be pulled. And when I get up to leave, I want
to see which repositories have changes that need to be pushed. Not to
mention files that need committed, loose objects that need packed, etc.
So I wrote the 'git-stale' script, included below. It's not especially
user-friendly, but you might find it useful, as it solves the exact
problem you are talking about (and much more).
It reads 'repository specifications' from ~/.gitstale, one per line,
which are either of the form:
/path/to/repo
which specifies a repo to check, or:
r:/path/to/many/repos
which specifies a hierarchy in which to recursively find repos.
My .gitstale looks something like this:
/home/peff/compile/git
/home/peff/compile/tig
r:/home/peff/work
and I get output something like this (edited for brevity):
Checking (1/77) /home/peff/compile/git...
Checking (2/77) /home/peff/compile/tig...
[...]
Checking (77/77) /home/peff/work/foo...
MERGE:next /home/peff/compile/git
COMMIT: /home/peff/work/foo
PACK: /home/peff/work/foo
PUSH:master /home/peff/work/bar
which translates to:
- the git repo has commits in 'origin/next' that are not in 'next'
(and you might want to merge them in)
- there are uncommitted files in 'foo'
- 'foo' needs packing
- in the 'bar' repo there are commits in master that are not in origin
(and you might want to push)
Hopefully it will be useful to you, though I think it is probably too
specific to my workflow to be part of git.
-Peff
-- >8 --
#!/usr/bin/perl
use strict;
use Getopt::Long;
my $CONFIG_FILE = "$ENV{HOME}/.gitstale";
my $nofetch = $ENV{GITSTALE_NOFETCH};
Getopt::Long::Configure(qw(bundling));
GetOptions('nofetch|n!' => \$nofetch) or exit 100;
my @projects = process_spec(@ARGV ? @ARGV : cat($CONFIG_FILE));
my $n = 1;
my $total = @projects;
my %errors;
foreach my $p (@projects) {
print "Checking ($n/$total) $p...\n";
$errors{$p} = [check_git($p)];
$n++;
}
my $errcount;
foreach my $p (@projects) {
foreach my $e (@{$errors{$p}}) {
print "$e: $p\n";
}
}
exit $errcount ? 1 : 0;
sub cat {
my $fn = shift;
open(my $fh, '<', $fn)
or die "unable to open $fn: $!\n";
return map { chomp; length($_) ? $_ : () } <$fh>;
}
sub process_spec {
my @dirs;
my @roots;
my @exclude;
foreach (@_) {
if(/^r:(.*)/) { push @roots, $1 }
elsif(/^d:(.*)/) { push @dirs, $1 }
elsif(/^-(.*)/) { push @exclude, qr#(^|/)$1($|/)# }
else { push @dirs, $_ }
}
use File::Find;
find({
no_chdir => 1,
preprocess => sub { sort @_ },
wanted => sub {
return unless -d $_ && $_ =~ m#/.git$#;
foreach my $e (@exclude) { return if $_ =~ $e }
my $d = $_;
$d =~ s#/\.git$##;
push @dirs, $d;
}
}, @roots) if @roots;
return @dirs;
}
sub count_zero {
open(my $fh, '-|', @_) or die "unable to fork: $!\n";
my $line = <$fh>;
return length($line) == 0;
}
sub check_git {
my $d = shift;
chdir($d) or return 'CHDIR';
my @r;
count_zero(qw(
git-ls-files -m -o -d --exclude-per-directory=.gitignore
--directory --no-empty-directory
)) or push @r, 'COMMIT';
if(has_origin()) {
push @r, 'FETCH' if !$nofetch && system('git-fetch');
foreach my $p (branch_pairs()) {
count_zero('git-rev-list', "$p->[0]..$p->[1]")
or push @r, "MERGE:$p->[0]";
count_zero('git-rev-list', "$p->[1]..$p->[0]")
or push @r, "PUSH:$p->[0]";
}
}
else {
push @r, 'ORIGIN';
}
push @r, 'PACK' if unpacked_objects() > 1000;
return @r;
}
sub unpacked_objects {
my $objects = `git-count-objects`;
$objects =~ /^(\d+)/;
return $1;
}
sub branch_pairs {
my %config;
foreach my $line (`git-repo-config --get-regexp 'branch..*..*'`) {
$line =~ m#^branch\.([^.]+)\.([^ ]+) (?:refs/heads/)?(.*)#
or die "confusing git-repo-config output: $line\n";
$config{$1}{$2} = $3;
}
return [qw(master origin)] if -e '.git/refs/heads/origin';
return
(-e '.git/refs/heads/origin' ? [qw(master origin)] : ()),
map {
$config{$_}{remote} && $config{$_}{merge} ?
[$_, $config{$_}{remote} . '/' . $config{$_}{merge}] :
()
} sort keys(%config);
}
sub has_origin {
return
-e '.git/branches/origin' ||
-e '.git/remotes/origin' ||
!count_zero(qw(git-repo-config --get remote.origin.url));
}
__END__
-
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html