git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[RFC] git-remote

From
Junio C Hamano <junkio@cox.net>
Date
Jan 3, 2007, 21:40 UTC
Message-ID
<7vfyarokk7.fsf@assigned-by-dhcp.cox.net>

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@cox.net>
---
 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
Next: Jakub Narebski
Message 1 of 16 in “[RFC] git-remote”
  1. Junio C HamanoJan 3, 2007
  2. Jakub NarebskiJan 3, 2007
  3. Shawn O. PearceJan 5, 2007
  4. Junio C HamanoJan 5, 2007
  5. Shawn O. PearceJan 5, 2007
  6. Santi BéjarJan 5, 2007
  7. Johannes SchindelinJan 5, 2007
  8. Santi BéjarJan 5, 2007
  9. Carl WorthJan 5, 2007
  10. Junio C HamanoJan 6, 2007
  11. Carl WorthJan 8, 2007
  12. J. Bruce FieldsJan 9, 2007
  13. Junio C HamanoJan 9, 2007
  14. Junio C HamanoJan 9, 2007
  15. Andreas EricssonJan 9, 2007
  16. J. Bruce FieldsJan 10, 2007

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.