It might be handy to have a single command that helps you manage your configuration that relates to downloading from remote repositories. This currently does only about 20% of what I want it to do. $ git remote shows the list of 'remotes' you have defined somewhere, and $ git remote origin shows the details about the named remote (in this case "origin"). How the branches are tracked, if you have a tracking branch that is stale, etc. $ git add another git://git.kernel.org/pub/... defines the default remote.another.url and remote.another.fetch entries just like a clone does; you can say "git fetch another" afterwards. For it to be useful, I think it should be enhanced to: - check overlaps of tracking branches and warn; - offer to remove stale tracking branches in one go; - offer ways to remove or rename remote; - give different levels of verbosity in its 'show' command, especially if ls-remote is run; - offer ways to update an existing remote, perhaps have an interactive mode; Other enhancements might be also possible, but I do not think of anything that is absolutely necessary other than the above right now. Signed-off-by: Junio C Hamano <junkio@xxxxxxx> --- Makefile | 2 +- git-remote.perl | 260 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+), 1 deletions(-) diff --git a/Makefile b/Makefile index fa1a022..ea07634 100644 --- a/Makefile +++ b/Makefile @@ -179,7 +179,7 @@ SCRIPT_SH = \ SCRIPT_PERL = \ git-add--interactive.perl \ git-archimport.perl git-cvsimport.perl git-relink.perl \ - git-cvsserver.perl \ + git-cvsserver.perl git-remote.perl \ git-svnimport.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl diff --git a/git-remote.perl b/git-remote.perl new file mode 100755 index 0000000..b0ec838 --- /dev/null +++ b/git-remote.perl @@ -0,0 +1,260 @@ +#!/usr/bin/perl -w + +use Git; +my $git = Git->repository(); + +sub add_remote_config { + my ($hash, $name, $what, $value) = @_; + if ($what eq 'url') { + if (exists $hash->{$name}{'URL'}) { + print STDERR "Warning: more than one remote.$name.url\n"; + } + $hash->{$name}{'URL'} = $value; + } + elsif ($what eq 'fetch') { + $hash->{$name}{'FETCH'} ||= []; + push @{$hash->{$name}{'FETCH'}}, $value; + } + if (!exists $hash->{$name}{'SOURCE'}) { + $hash->{$name}{'SOURCE'} = 'config'; + } +} + +sub add_remote_remotes { + my ($hash, $file, $name) = @_; + + if (exists $hash->{$name}) { + $hash->{$name}{'WARNING'} = 'ignored due to config'; + return; + } + + my $fh; + if (!open($fh, '<', $file)) { + print STDERR "Warning: cannot open $file\n"; + return; + } + my $it = { 'SOURCE' => 'remotes' }; + $hash->{$name} = $it; + while (<$fh>) { + chomp; + if (/^URL:\s*(.*)$/) { + # Having more than one is Ok -- it is used for push. + if (! exists $it->{'URL'}) { + $it->{'URL'} = $1; + } + } + elsif (/^Push:\s*(.*)$/) { + ; # later + } + elsif (/^Pull:\s*(.*)$/) { + $it->{'FETCH'} ||= []; + push @{$it->{'FETCH'}}, $1; + } + elsif (/^\#/) { + ; # ignore + } + else { + print STDERR "Warning: funny line in $file: $_\n"; + } + } + close($fh); +} + +sub list_remote { + my ($git) = @_; + my %seen = (); + + for ($git->command(qw(repo-config --get-regexp), '^remote\.')) { + if (/^remote\.([^.]*)\.(\S*)\s+(.*)$/) { + add_remote_config(\%seen, $1, $2, $3); + } + } + + my $dir = $git->repo_path() . "/remotes"; + if (opendir(my $dh, $dir)) { + local $_; + while ($_ = readdir($dh)) { + chomp; + next if (! -f "$dir/$_" || ! -r _); + add_remote_remotes(\%seen, "$dir/$_", $_); + } + } + + return \%seen; +} + +sub add_branch_config { + my ($hash, $name, $what, $value) = @_; + if ($what eq 'remote') { + if (exists $hash->{$name}{'REMOTE'}) { + print STDERR "Warning: more than one branch.$name.remote\n"; + } + $hash->{$name}{'REMOTE'} = $value; + } + elsif ($what eq 'merge') { + $hash->{$name}{'MERGE'} ||= []; + push @{$hash->{$name}{'MERGE'}}, $value; + } +} + +sub list_branch { + my ($git) = @_; + my %seen = (); + for ($git->command(qw(repo-config --get-regexp), '^branch\.')) { + if (/^branch\.([^.]*)\.(\S*)\s+(.*)$/) { + add_branch_config(\%seen, $1, $2, $3); + } + } + + return \%seen; +} + +my $remote = list_remote($git); +my $branch = list_branch($git); + +sub update_ls_remote { + my ($harder, $info) = @_; + + return if (($harder == 0) || + (($harder == 1) && exists $info->{'LS_REMOTE'})); + + my @ref = map { + s|^[0-9a-f]{40}\s+refs/heads/||; + $_; + } $git->command(qw(ls-remote --heads), $info->{'URL'}); + $info->{'LS_REMOTE'} = \@ref; +} + +sub show_wildcard_mapping { + my ($forced, $ours, $ls) = @_; + my %refs; + for (@$ls) { + $refs{$_} = 01; # bit #0 to say "they have" + } + for ($git->command('for-each-ref', "refs/remotes/$ours")) { + chomp; + next unless (s|^[0-9a-f]{40}\s[a-z]+\srefs/remotes/$ours/||); + next if ($_ eq 'HEAD'); + $refs{$_} ||= 0; + $refs{$_} |= 02; # bit #1 to say "we have" + } + my (@new, @stale, @tracked); + for (sort keys %refs) { + my $have = $refs{$_}; + if ($have == 1) { + push @new, $_; + } + elsif ($have == 2) { + push @stale, $_; + } + elsif ($have == 3) { + push @tracked, $_; + } + } + if (@new) { + print " New remote branches (next fetch will store in remotes/$ours)\n"; + print " @new\n"; + } + if (@stale) { + print " Stale tracking branches in remotes/$ours (you'd better remove them)\n"; + print " @stale\n"; + } + if (@tracked) { + print " Tracked remote branches\n"; + print " @tracked\n"; + } +} + +sub show_mapping { + my ($name, $info) = @_; + my $fetch = $info->{'FETCH'}; + my $ls = $info->{'LS_REMOTE'}; + my (@stale, @tracked); + + for (@$fetch) { + next unless (/(\+)?([^:]+):(.*)/); + my ($forced, $theirs, $ours) = ($1, $2, $3); + if ($theirs eq 'refs/heads/*' && + $ours =~ /^refs\/remotes\/(.*)\/\*$/) { + # wildcard mapping + show_wildcard_mapping($forced, $1, $ls); + } + elsif ($theirs =~ /\*/ || $ours =~ /\*/) { + print STDERR "Warning: unrecognized mapping in remotes.$name.fetch: $_\n"; + } + elsif ($theirs =~ s|^refs/heads/||) { + if (!grep { $_ eq $theirs } @$ls) { + push @stale, $theirs; + } + elsif ($ours ne '') { + push @tracked, $theirs; + } + } + } + if (@stale) { + print " Stale tracking branches in remotes/$name (you'd better remove them)\n"; + print " @stale\n"; + } + if (@tracked) { + print " Tracked remote branches\n"; + print " @tracked\n"; + } +} + +sub show_remote { + my ($name, $ls_remote) = @_; + if (!exists $remote->{$name}) { + print STDERR "No such remote $name\n"; + return; + } + my $info = $remote->{$name}; + update_ls_remote($ls_remote, $info); + + print "* remote $name\n"; + print " URL: $info->{'URL'}\n"; + for my $branchname (sort keys %$branch) { + next if ($branch->{$branchname}{'REMOTE'} ne $name); + my @merged = map { + s|^refs/heads/||; + $_; + } split(' ',"@{$branch->{$branchname}{'MERGE'}}"); + next unless (@merged); + print " Remote branch(es) merged with 'git pull' while on branch $branchname\n"; + print " @merged\n"; + } + if ($info->{'LS_REMOTE'}) { + show_mapping($name, $info); + } +} + +sub add_remote { + my ($name, $url) = @_; + if (exists $remote->{$name}) { + print STDERR "remote $name already exists.\n"; + exit(1); + } + $git->command('repo-config', "remote.$name.url", $url); + $git->command('repo-config', "remote.$name.fetch", + "+refs/heads/*:refs/remotes/$name/*"); +} + +if (!@ARGV) { + for (sort keys %$remote) { + print "$_\n"; + } +} +elsif ($ARGV[0] eq 'show') { + if (@ARGV != 2) { + print STDERR "Usage: git remote show <remote>\n"; + exit(1); + } + show_remote($ARGV[1], 1); +} +elsif ($ARGV[0] eq 'add') { + if (@ARGV != 3) { + print STDERR "Usage: git remote show <name> <url>\n"; + exit(1); + } + add_remote($ARGV[1], $ARGV[2]); +} + -- 1.5.0.rc0.gcc26 - To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html