git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [wishlist] git branch -d -r remotename
@ 2007-03-18  9:36 Sam Vilain
  2007-03-18 11:01 ` Sam Vilain
  0 siblings, 1 reply; 8+ messages in thread
From: Sam Vilain @ 2007-03-18  9:36 UTC (permalink / raw)
  To: git

I'm finding myself wanting to delete all local branch heads that are
already present on a remote.

You wouldn't want it to be too eager about it, and certainly you
wouldn't want to use it against any server that isn't considered
completely "upstream" of the repository you're doing it on, otherwise if
both ends run it and then "git remote prune" the refs will go away.

I think either "git remote prune" or "git branch -d" would be a good
place for that.

Thoughts?

Sam.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [wishlist] git branch -d -r remotename
  2007-03-18  9:36 [wishlist] git branch -d -r remotename Sam Vilain
@ 2007-03-18 11:01 ` Sam Vilain
  2007-03-18 11:01   ` Sam Vilain
  0 siblings, 1 reply; 8+ messages in thread
From: Sam Vilain @ 2007-03-18 11:01 UTC (permalink / raw)
  To: git

> I'm finding myself wanting to delete all local branch heads that are
> already present on a remote.

Something like this.

Subject: [PATCH] git-remote: implement prune -c

It would be nice to prune local refs which are irrelevant; add an
option to git-remote prune, with documentation.

Signed-off-by: Sam Vilain <sam@vilain.net>
---
 Documentation/git-remote.txt |    4 ++++
 git-remote.perl              |   33 +++++++++++++++++++++++++++++++--
 2 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index ab04b86..7381b08 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -63,6 +63,10 @@ referenced by <name>, but are still locally available in
 With `-n` option, the remote heads are not confirmed first with `git
 ls-remote <name>`; cached information is used instead.  Use with
 caution.
++
+With `-c` option, all *local* branches that can be found via this
+remote are removed.  Useful for cleaning up old style remote branches,
+or temporary branches.
 
 'fetch'::
 
diff --git a/git-remote.perl b/git-remote.perl
index 20f9f54..e74bce2 100755
--- a/git-remote.perl
+++ b/git-remote.perl
@@ -210,7 +210,7 @@ sub show_mapping {
 }
 
 sub prune_remote {
-	my ($name, $ls_remote) = @_;
+	my ($name, $ls_remote, $clean_local) = @_;
 	if (!exists $remote->{$name}) {
 		print STDERR "No such remote $name\n";
 		return;
@@ -224,6 +224,32 @@ sub prune_remote {
 		my @v = $git->command(qw(rev-parse --verify), "$prefix/$to_prune");
 		$git->command(qw(update-ref -d), "$prefix/$to_prune", $v[0]);
 	}
+	if ( $clean_local ) {
+		my @refs = map { (split " ")[0] }
+			$git->command(qw(for-each-ref), $prefix);
+		print "Saw ".@refs." remote refs\n";
+		my %r = map { ($_ => undef) }
+			$git->command("rev-list", @refs);
+		print "Saw ".(keys %r)." revs\n";
+		my %l = map { (split " ")[2,0] }
+			$git->command(qw(for-each-ref refs/heads));
+		print "Saw ".(keys %l)." local heads\n";
+
+		# don't delete the current branch, but there must be a
+		# better way to find it out
+		chomp(my ($checked_out) = map { /^\* (.*)/ }
+			$git->command("branch"));
+		$checked_out = "refs/heads/$checked_out"
+		    if $checked_out;
+
+		while ( my ($ref, $rev) = each %l ) {
+			next if $checked_out and $ref eq $checked_out;
+			if ( exists $r{$rev} ) {
+				print "$ref is obsolete\n";
+				$git->command(qw(update-ref -d), $ref, $rev);
+			}
+		}
+	}
 }
 
 sub show_remote {
@@ -344,6 +370,9 @@ elsif ($ARGV[0] eq 'prune') {
 		if ($ARGV[$i] eq '-n') {
 			$ls_remote = 0;
 		}
+		elsif ($ARGV[$i] eq '-c') {
+			$clean_local = 1;
+		}
 		else {
 			last;
 		}
@@ -353,7 +382,7 @@ elsif ($ARGV[0] eq 'prune') {
 		exit(1);
 	}
 	for (; $i < @ARGV; $i++) {
-		prune_remote($ARGV[$i], $ls_remote);
+		prune_remote($ARGV[$i], $ls_remote, $clean_local);
 	}
 }
 elsif ($ARGV[0] eq 'add') {
-- 
1.5.0.4.210.gf8a7c-dirty

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [wishlist] git branch -d -r remotename
  2007-03-18 11:01 ` Sam Vilain
@ 2007-03-18 11:01   ` Sam Vilain
  2007-03-18 19:42     ` Junio C Hamano
  0 siblings, 1 reply; 8+ messages in thread
From: Sam Vilain @ 2007-03-18 11:01 UTC (permalink / raw)
  To: git

One time again, this time with the call to porcelain command
`git-branch` replaced with the plumbing command `symbolic-ref HEAD`

I also changed the output to be a little less "Got here\n" style.

Subject: [PATCH] git-remote: implement prune -c

It would be nice to prune local refs which are irrelevant; add an
option to git-remote prune, with documentation.

Signed-off-by: Sam Vilain <sam@vilain.net>
---
 Documentation/git-remote.txt |    4 ++++
 git-remote.perl              |   29 +++++++++++++++++++++++++++--
 2 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index ab04b86..7381b08 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -63,6 +63,10 @@ referenced by <name>, but are still locally available in
 With `-n` option, the remote heads are not confirmed first with `git
 ls-remote <name>`; cached information is used instead.  Use with
 caution.
++
+With `-c` option, all *local* branches that can be found via this
+remote are removed.  Useful for cleaning up old style remote branches,
+or temporary branches.
 
 'fetch'::
 
diff --git a/git-remote.perl b/git-remote.perl
index 20f9f54..0ed8a77 100755
--- a/git-remote.perl
+++ b/git-remote.perl
@@ -210,7 +210,7 @@ sub show_mapping {
 }
 
 sub prune_remote {
-	my ($name, $ls_remote) = @_;
+	my ($name, $ls_remote, $clean_local) = @_;
 	if (!exists $remote->{$name}) {
 		print STDERR "No such remote $name\n";
 		return;
@@ -224,6 +224,28 @@ sub prune_remote {
 		my @v = $git->command(qw(rev-parse --verify), "$prefix/$to_prune");
 		$git->command(qw(update-ref -d), "$prefix/$to_prune", $v[0]);
 	}
+	if ( $clean_local ) {
+		my @refs = map { (split " ")[0] }
+			$git->command(qw(for-each-ref), $prefix);
+		print "fetching revs for ".@refs." remote refs\n";
+		my %r = map { ($_ => undef) }
+			$git->command("rev-list", @refs);
+		print "fetched ".(keys %r)." revs\n";
+		my %l = map { (split " ")[2,0] }
+			$git->command(qw(for-each-ref refs/heads));
+		print "checking ".(keys %l)." local heads\n";
+
+		# don't delete the current branch
+		my ($checked_out) = $git->command(qw(symbolic-ref HEAD));
+
+		while ( my ($ref, $rev) = each %l ) {
+			next if $checked_out and $ref eq $checked_out;
+			if ( exists $r{$rev} ) {
+				print "$ref is obsolete\n";
+				$git->command(qw(update-ref -d), $ref, $rev);
+			}
+		}
+	}
 }
 
 sub show_remote {
@@ -344,6 +366,9 @@ elsif ($ARGV[0] eq 'prune') {
 		if ($ARGV[$i] eq '-n') {
 			$ls_remote = 0;
 		}
+		elsif ($ARGV[$i] eq '-c') {
+			$clean_local = 1;
+		}
 		else {
 			last;
 		}
@@ -353,7 +378,7 @@ elsif ($ARGV[0] eq 'prune') {
 		exit(1);
 	}
 	for (; $i < @ARGV; $i++) {
-		prune_remote($ARGV[$i], $ls_remote);
+		prune_remote($ARGV[$i], $ls_remote, $clean_local);
 	}
 }
 elsif ($ARGV[0] eq 'add') {
-- 
1.5.0.4.210.gf8a7c-dirty

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [wishlist] git branch -d -r remotename
  2007-03-18 11:01   ` Sam Vilain
@ 2007-03-18 19:42     ` Junio C Hamano
  2007-03-18 21:46       ` Sam Vilain
  0 siblings, 1 reply; 8+ messages in thread
From: Junio C Hamano @ 2007-03-18 19:42 UTC (permalink / raw)
  To: Sam Vilain; +Cc: git

Sam Vilain <sam@vilain.net> writes:

> One time again, this time with the call to porcelain command
> `git-branch` replaced with the plumbing command `symbolic-ref HEAD`
>
> I also changed the output to be a little less "Got here\n" style.
>
> Subject: [PATCH] git-remote: implement prune -c
>
> It would be nice to prune local refs which are irrelevant; add an
> option to git-remote prune, with documentation.

I do not understand what workflow you are assuming, so your use
of the word "irrelevant" does not mean much to me.  I suspect
other readers of the patch and documentation wouldn't find it
clear in what situation this option is useful.

Perhaps you are thinking about this scenario?  I am only
guessing because you are not clear enough:

	$ git clone
        ... time passes ...
        $ git checkout -b next origin/next
        ... build, install, have fun ...
	$ git checkout master
        ... time passes ...
        $ git branch
        ... notice that you do not hack on your copy of 'next'
        ... and want to remove it
	$ git remote prune -c

In any case, are you checking irrelevancy?  What if your foo branch has
more changes to be sent upstream?  Even when the remote has a
bit older version doesn't your code remove yours?  For example,
if you did this, instead of the above, what happens?

	$ git clone
        ... time passes ...
        $ git checkout -b next origin/next
        ... build, install, have fun ...
	... find an opportunity to improve ...
        $ edit
        $ git commit ;# on your 'next'.
        ... build, install, test ...
	$ git checkout master
        ... time passes ...
        $ git branch
        ... notice that you do not hack on your copy of 'next' anymore,
        ... and want to remove it
	$ git remote prune -c

If the above is the usage scenario you are trying to help, then
wouldn't it be helpful if you could also help removing 'my-next'
in this slightly altered example?

	$ git clone
        ... time passes ...
        $ git checkout -b my-next origin/next
        ... build, install, have fun ...
	$ git checkout master
        ... time passes ...
        $ git branch
        ... notice that you do not hack on your copy of 'next'
        ... which is 'my-next', and want to remove it
	$ git remote prune -c

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [wishlist] git branch -d -r remotename
  2007-03-18 19:42     ` Junio C Hamano
@ 2007-03-18 21:46       ` Sam Vilain
  2007-03-19  6:18         ` Junio C Hamano
  0 siblings, 1 reply; 8+ messages in thread
From: Sam Vilain @ 2007-03-18 21:46 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Junio C Hamano wrote:
>> Subject: [PATCH] git-remote: implement prune -c
>>
>> It would be nice to prune local refs which are irrelevant; add an
>> option to git-remote prune, with documentation.
>>     
>
> I do not understand what workflow you are assuming, so your use
> of the word "irrelevant" does not mean much to me.  I suspect
> other readers of the patch and documentation wouldn't find it
> clear in what situation this option is useful.
>   

Bad choice of words. What I mean is to delete all local refs which are
already reachable by a remote ref on the given remote.

> Perhaps you are thinking about this scenario?  I am only
> guessing because you are not clear enough:
>
> 	$ git clone
>         ... time passes ...
>         $ git checkout -b next origin/next
>         ... build, install, have fun ...
> 	$ git checkout master
>         ... time passes ...
>         $ git branch
>         ... notice that you do not hack on your copy of 'next'
>         ... and want to remove it
> 	$ git remote prune -c
>   

Yes, that's it. Or clean up the references you already pushed because
they are no longer of interest.

> In any case, are you checking irrelevancy?  What if your foo branch has
> more changes to be sent upstream?  Even when the remote has a
> bit older version doesn't your code remove yours?  For example,
> if you did this, instead of the above, what happens?
>
> 	$ git clone
>         ... time passes ...
>         $ git checkout -b next origin/next
>         ... build, install, have fun ...
> 	... find an opportunity to improve ...
>         $ edit
>         $ git commit ;# on your 'next'.
>         ... build, install, test ...
> 	$ git checkout master
>         ... time passes ...
>         $ git branch
>         ... notice that you do not hack on your copy of 'next' anymore,
>         ... and want to remove it
> 	$ git remote prune -c
>   

It doesn't do that because the head doesn't match any revision that was
given to us by `rev-list refs/remotes/foo/*`

> If the above is the usage scenario you are trying to help, then
> wouldn't it be helpful if you could also help removing 'my-next'
> in this slightly altered example?
>
> 	$ git clone
>         ... time passes ...
>         $ git checkout -b my-next origin/next
>         ... build, install, have fun ...
> 	$ git checkout master
>         ... time passes ...
>         $ git branch
>         ... notice that you do not hack on your copy of 'next'
>         ... which is 'my-next', and want to remove it
> 	$ git remote prune -c

Yes, the idea was to "sweep" all branches that were just local branches
of a remote and never worked on. This is most useful right now for
people switching from Cogito or old-style remotes, who have a lot of
branches that are remote tracking branches. Using this, they can just
set up a new remote, fetch and prune -c and be left in a tidy state.

Sam.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [wishlist] git branch -d -r remotename
  2007-03-18 21:46       ` Sam Vilain
@ 2007-03-19  6:18         ` Junio C Hamano
  2007-03-19  6:40           ` Junio C Hamano
  0 siblings, 1 reply; 8+ messages in thread
From: Junio C Hamano @ 2007-03-19  6:18 UTC (permalink / raw)
  To: Sam Vilain; +Cc: git

Sam Vilain <sam@vilain.net> writes:

> It doesn't do that because the head doesn't match any revision that was
> given to us by `rev-list refs/remotes/foo/*`

Gaah.  That goes all the way down to the root commit.

+		print "fetching revs for ".@refs." remote refs\n";

Is this meant to be final message to the end-user, or debug?

+		my %r = map { ($_ => undef) }
+			$git->command("rev-list", @refs);

This traverses all the way down to the root commit, doesn't it?

That is probably good enough for a toy repository for testing,
but is impractical in real repositories, I am afraid.  See below
for the standard ways to check ancestry.

+		# don't delete the current branch
+		my ($checked_out) = $git->command(qw(symbolic-ref HEAD));

When the HEAD is detached, does the error message go directly to
the end user?

+		while ( my ($ref, $rev) = each %l ) {
+			next if $checked_out and $ref eq $checked_out;
+			if ( exists $r{$rev} ) {
+				print "$ref is obsolete\n";
+				$git->command(qw(update-ref -d), $ref, $rev);
+			}
+		}
 
The standard way to check if commit A is included in (i.e. is an
ancestor of) commit B, without traversing the ancestry chain of
B all the way down to the root commit, is to run:

	git merge-base --all A B

and see if A appears in its output (if so, then A is an ancestor
of B, otherwise it is not).  This is a pair-wise check, and for
your purpose the check would become N*M operation (Yuck).

The same check can be done in parallel with:

	git show-branch --independent A B C D...

whose output would include A if the commit is not included in
any of the other commits B C D...  This parallel traversal has a
limit --- you can only check 25 branches at a time.

>> If the above is the usage scenario you are trying to help, then
>> wouldn't it be helpful if you could also help removing 'my-next'
>> in this slightly altered example?
>>
>> 	$ git clone
>>         ... time passes ...
>>         $ git checkout -b my-next origin/next
>>         ... build, install, have fun ...
>> 	$ git checkout master
>>         ... time passes ...
>>         $ git branch
>>         ... notice that you do not hack on your copy of 'next'
>>         ... which is 'my-next', and want to remove it
>> 	$ git remote prune -c
>
> Yes, the idea was to "sweep" all branches that were just local branches
> of a remote and never worked on. This is most useful right now for
> people switching from Cogito or old-style remotes, who have a lot of
> branches that are remote tracking branches. Using this, they can just
> set up a new remote, fetch and prune -c and be left in a tidy state.

You make it sound like this is just a one-shot conversion issue,
in which case I really doubt we would want that.  But it appears
to be a useful feature in general, provided if the assumed use
case is described clearly so that new users know when to use it.
In the form that was given to me, I think the documentation
leaves the user in a "Huh?" state.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [wishlist] git branch -d -r remotename
  2007-03-19  6:18         ` Junio C Hamano
@ 2007-03-19  6:40           ` Junio C Hamano
  2007-03-19 23:37             ` (unknown) Sam Vilain
  0 siblings, 1 reply; 8+ messages in thread
From: Junio C Hamano @ 2007-03-19  6:40 UTC (permalink / raw)
  To: Sam Vilain; +Cc: git

Junio C Hamano <junkio@cox.net> writes:

> The standard way to check if commit A is included in (i.e. is an
> ancestor of) commit B, without traversing the ancestry chain of
> B all the way down to the root commit, is to run:
>
> 	git merge-base --all A B
>
> and see if A appears in its output (if so, then A is an ancestor
> of B, otherwise it is not).  This is a pair-wise check, and for
> your purpose the check would become N*M operation (Yuck).
>
> The same check can be done in parallel with:
>
> 	git show-branch --independent A B C D...
>
> whose output would include A if the commit is not included in
> any of the other commits B C D...  This parallel traversal has a
> limit --- you can only check 25 branches at a time.

Well, I was silly.  If you want to see if A is an ancestor of
any of B C D..., the standard and most efficient way to do so is
with rev-list.

	git rev-list A --not B C D...

will show _nothing_ only when A is an ancestor of one (or more)
of B C D..., so you invoke it and upon getting the first line of
output you declare A cannot be removed without reading the
remainder of the output.

show-branch --independent is an overkill for your purpose as it
does not treat A any more special from others (iow, it checks if
B is contained in A C D..., C is contained in A B D... all in
parallel), and you are not interested in finding out how remote
refs are related with each other.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* (unknown)
  2007-03-19  6:40           ` Junio C Hamano
@ 2007-03-19 23:37             ` Sam Vilain
  0 siblings, 0 replies; 8+ messages in thread
From: Sam Vilain @ 2007-03-19 23:37 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Junio C Hamano wrote:
> If you want to see if A is an ancestor of
> any of B C D..., the standard and most efficient way to do so is
> with rev-list.
>
> 	git rev-list A --not B C D...
>
> will show _nothing_ only when A is an ancestor of one (or more)
> of B C D..., so you invoke it and upon getting the first line of
> output you declare A cannot be removed without reading the
> remainder of the output.

Sure.  I figured the commands I put would only use about 30MB for a
300,000 commit repository, so it wouldn't be too bad.  But I didn't
think of that.

Here we go, also based upon 'next' rather than the result of my
previous patches of the day.

Subject: [PATCH] git-remote: implement prune -c

It would be nice to be able to prune local refs which just point to
some place in the remote; add an option to git-remote prune, with
documentation.

Signed-off-by: Sam Vilain <sam.vilain@catalyst.net.nz>
---
 Documentation/git-remote.txt |    5 +++++
 git-remote.perl              |   25 +++++++++++++++++++++++--
 2 files changed, 28 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index f96b304..6a28e6c 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -54,6 +54,11 @@ Gives some information about the remote <name>.
 Deletes all stale tracking branches under <name>.
 These stale branches have already been removed from the remote repository
 referenced by <name>, but are still locally available in "remotes/<name>".
++
+With `-c` option, all *local* branches that exist in the history of
+any of the mirrored heads from the remote are removed.  This is useful
+for discarding temporary local branches that you did not make any
+commits to.
 
 
 DISCUSSION
diff --git a/git-remote.perl b/git-remote.perl
index 670bafb..84ac534 100755
--- a/git-remote.perl
+++ b/git-remote.perl
@@ -210,7 +210,7 @@ sub show_mapping {
 }
 
 sub prune_remote {
-	my ($name, $ls_remote) = @_;
+	my ($name, $ls_remote, $clean_local) = @_;
 	if (!exists $remote->{$name}) {
 		print STDERR "No such remote $name\n";
 		return;
@@ -224,6 +224,24 @@ sub prune_remote {
 		my @v = $git->command(qw(rev-parse --verify), "$prefix/$to_prune");
 		$git->command(qw(update-ref -d), "$prefix/$to_prune", $v[0]);
 	}
+	if ( $clean_local ) {
+		my @remote = map { (split " ")[0] }
+			$git->command(qw(for-each-ref), $prefix);
+		my %local = map { (split " ")[2,0] }
+			$git->command(qw(for-each-ref refs/heads));
+		my %not_remote = map { ($_ => undef) }
+			$git->command("rev-list", keys %local, "--not", @remote);
+		# don't delete the current branch
+		my ($checked_out) = $git->command(qw(symbolic-ref HEAD));
+
+		while ( my ($ref, $rev) = each %local ) {
+			next if $checked_out and $ref eq $checked_out;
+			if ( not exists $not_remote{$rev} ) {
+				print "$ref is obsolete\n";
+				$git->command(qw(update-ref -d), $ref, $rev);
+			}
+		}
+	}
 }
 
 sub show_remote {
@@ -310,6 +328,9 @@ elsif ($ARGV[0] eq 'prune') {
 		if ($ARGV[$i] eq '-n') {
 			$ls_remote = 0;
 		}
+		elsif ($ARGV[$i] eq '-c') {
+			$clean_local = 1;
+		}
 		else {
 			last;
 		}
@@ -319,7 +340,7 @@ elsif ($ARGV[0] eq 'prune') {
 		exit(1);
 	}
 	for (; $i < @ARGV; $i++) {
-		prune_remote($ARGV[$i], $ls_remote);
+		prune_remote($ARGV[$i], $ls_remote, $clean_local);
 	}
 }
 elsif ($ARGV[0] eq 'add') {
-- 
1.5.0.2.214.gb318

^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2007-03-19 23:47 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2007-03-18  9:36 [wishlist] git branch -d -r remotename Sam Vilain
2007-03-18 11:01 ` Sam Vilain
2007-03-18 11:01   ` Sam Vilain
2007-03-18 19:42     ` Junio C Hamano
2007-03-18 21:46       ` Sam Vilain
2007-03-19  6:18         ` Junio C Hamano
2007-03-19  6:40           ` Junio C Hamano
2007-03-19 23:37             ` (unknown) Sam Vilain

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).