threads / discuss / 11453

Retroactively change email signature?

Subject: Retroactively change email signature?

## tl;dr

9 messages between Jan 2, 2008 and Jan 4, 2008.

replies: 8people: 5as markdown or json

Stephen Sinclair· Jan 2, 2008, 20:37 UTC · lore
Hi,

I imagine that changing old commits is considered one of the great evils of source control, but I have a small problem: I have a habit of testing my code on several different computers and operating systems. Sometimes when setting up a new computer for testing (and thus, for development, since testing always results in a few commits), I have forgotten to use git-config to configure my name and email address.

Since git doesn't warn when I've forgotten to do this, my git-log is now sprinkled with various email addresses, some which are just my username@hostname. This is completely useless to people who might view my public repository.

Is it possible to retroactively change the author and email of several commits? Perhaps some sort of search-and-replace for the commit metadata? Even for older commits, I'd like to change the email addresses to my current address.

If it's not possible it's not the end of the world, but I thought it wouldn't hurt to ask.

thanks, Steve

Jakub Narebski· Jan 2, 2008, 21:17 UTC · re: Stephen Sinclair · lore

Re: Retroactively change email signature?

"Stephen Sinclair" <radarsat1@gmail.com> writes:
Show 12 quoted lines
> I imagine that changing old commits is considered one of the great
> evils of source control, but I have a small problem:
> I have a habit of testing my code on several different computers and
> operating systems.  Sometimes when setting up a new computer for
> testing (and thus, for development, since testing always results in a
> few commits), I have forgotten to use git-config to configure my name
> and email address.
> 
> Since git doesn't warn when I've forgotten to do this, my git-log is
> now sprinkled with various email addresses, some which are just my
> username@hostname.  This is completely useless to people who might
> view my public repository.
This most certainly should get addressed IMHO.
 
Show 7 quoted lines
> Is it possible to retroactively change the author and email of several
> commits?  Perhaps some sort of search-and-replace for the commit
> metadata?  Even for older commits, I'd like to change the email
> addresses to my current address.
> 
> If it's not possible it's not the end of the world, but I thought it
> wouldn't hurt to ask.

You can use git-filter-branch(1), but please remember that it is practically rewriting whole repository, so you should do this _before_ you publish any changes.

-- 
Jakub Narebski
Poland
ShadeHawk on #git
Linus Torvalds· Jan 2, 2008, 21:24 UTC · re: Stephen Sinclair · lore

Re: Retroactively change email signature?

On Wed, 2 Jan 2008, Stephen Sinclair wrote:
Show 5 quoted lines
> 
> Is it possible to retroactively change the author and email of several
> commits?  Perhaps some sort of search-and-replace for the commit
> metadata?  Even for older commits, I'd like to change the email
> addresses to my current address.
See
	man git-filter-branch

and the "--env-filter" flag (and "--msg-filter" if you also want to change things like Sign-off's etc in the message text itself)

BUT! See also the big bold-face warning: using "filter-branch" will not change old commits, it will create a series of *new* commits that bear a striking resemblance to the old ones, but are not the same. So you'll have a totally new history.

That matters for things like
 - if you've already publicised the old history, you need to tell people 
   that they should fetch things anew
 - if you refer to the commit SHA1's elsewhere (like in commit messages 
   that revert previous commits), those SHA1's are still "valid", but they 
   refer to the *old* history, not the new history you created.

(I don't think git-filter-branch even exposes any easy way to remap the "revert commit xyz" messages in the message format. It's not technically hard to use a --msg-filter for it, but it seems a big bother).

		Linus
Stephen Sinclair· Jan 2, 2008, 22:06 UTC · re: Linus Torvalds · lore

Re: Retroactively change email signature?

Thanks, I was able to use git-filter-branch to accomplish what I wanted. I should have mentioned in the original email that it is not a widely distributed repo, so I'm not too worried about breaking things at this point. I guess for a larger project it might be a problem, so I probably just wouldn't do it.

> BUT! See also the big bold-face warning: using "filter-branch" will not
> change old commits, it will create a series of *new* commits that bear a
> striking resemblance to the old ones, but are not the same. So you'll have
> a totally new history.

Got it, thanks. I'll just have to remember to git-config properly next time I do this.

One thing that's a bit odd for me is that I use a different email address for this particular project.. Conveniently I'm able to use git-config to set an email address locally for this repo, however I have to remember to do this every time I clone it somewhere. Not a problem, necessarily, but perhaps just a matter of training myself.

For example, I have a clone of this repo on a Linux, Windows, and OS X machine, and also another clone for each on my laptop.. git is actually turning out to be great for doing all this swapping between machines, but it does take a little training to remember to set the configuration, since there's no central repo to ask me for my username and password before I can commit.

cheers, Steve

David Brown· Jan 3, 2008, 00:53 UTC · re: Stephen Sinclair · lore

Re: Retroactively change email signature?

On Wed, Jan 02, 2008 at 05:06:37PM -0500, Stephen Sinclair wrote:
Show 6 quoted lines
>For example, I have a clone of this repo on a Linux, Windows, and OS X
>machine, and also another clone for each on my laptop..  git is
>actually turning out to be great for doing all this swapping between
>machines, but it does take a little training to remember to set the
>configuration, since there's no central repo to ask me for my username
>and password before I can commit.

You can set the user.email and user.name globally with 'git config --global user.email ...' and the likes, but that only works if you use the same ID.

It'd be nice if there was something that could be put in the global user.email to require each repository to override that. On one machine, my password file doesn't have my name in it, and I kind of get this behavior by default, but it be nice to be able to do it for real.

Anyone have ideas on how this could be done?
Dave
Sam Vilain· Jan 3, 2008, 05:52 UTC · re: Linus Torvalds · lore

Re: Retroactively change email signature? [resend]

Linus Torvalds wrote:
> (I don't think git-filter-branch even exposes any easy way to remap the 
> "revert commit xyz" messages in the message format. It's not technically 
> hard to use a --msg-filter for it, but it seems a big bother).

Assuming, of course, that the commit you refer to in a message like that is in your history. If it's referring to another commit in a different branch, which may be being re-written, you've got two problems:

1. rewriting the commit
2. making sure that git-filter-branch processes the two commits in the
right order.

The first is easily fixable; the second requires a hook in git-filter-branch to customize the tournament.

Subject: [PATCH] git-filter-branch: allow a custom tournament for rewrite ordering

The topological sort by git rev-list may be inconvenient for history modification where commit IDs are used in comments to refer to other changes. So, allow for a program that can take a list of commits, and return that list in the order that the rewriter must follow. Borrow "Tournament" term from Graph Theory.

Signed-off-by: Sam Vilain <sam.vilain@catalyst.net.nz>
---
 Documentation/git-filter-branch.txt         |   18 ++++
 contrib/filter-branch/msg-links.perl        |   50 +++++++++
 contrib/filter-branch/tournament-links.perl |  148 +++++++++++++++++++++++++++
 git-filter-branch.sh                        |   11 ++
 t/t7003-filter-branch.sh                    |   21 ++++
 5 files changed, 248 insertions(+), 0 deletions(-)
 create mode 100755 contrib/filter-branch/msg-links.perl
 create mode 100755 contrib/filter-branch/tournament-links.perl
diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt
index 895d750..ed3a839 100644
--- a/Documentation/git-filter-branch.txt
+++ b/Documentation/git-filter-branch.txt
@@ -118,6 +118,24 @@ convenience functions, too.  For example, calling 'skip_commit "$@"'
 will leave out the current commit (but not its changes! If you want
 that, use gitlink:git-rebase[1] instead).
 
+--tournament <command>::
+	This command is responsible for re-ordering the commit history
+	after it is retrieved from the input list.  It is passed on
+	standard input the deduced list based upon a regular
+	topological search, and the command is expected to print to
+	standard output a list in the same form.
++
+Again, this can't be used to re-order patches like
+gitlink:git-rebase[1] does, as the tree IDs of the original positions
+will remain untouched, so the differences between revisions will be
+scrambled.
++
+However, it is required when you need to refer to a commit by ID in a
+commit message, where the commit you are referring to is on an
+unrelated branch.  See the contributed filters in the Git distribution
+for an example script that can let you preserve such referential
+integrity during a rewrite.
+
 --tag-name-filter <command>::
 	This is the filter for rewriting tag names. When passed,
 	it will be called for every tag ref that points to a rewritten
diff --git a/contrib/filter-branch/msg-links.perl b/contrib/filter-branch/msg-links.perl
new file mode 100755
index 0000000..1ff780c
--- /dev/null
+++ b/contrib/filter-branch/msg-links.perl
@@ -0,0 +1,50 @@
+#!/usr/bin/perl -nl
+#
+#   msg-links.perl
+#
+# This is the companion script to tournament-links.perl, it is
+# configured in the same way.  If you want this script, you will
+# probably also want the other one.
+#
+# What it does is rewrites all commit IDs of at least 12 digits long
+# to the post-rewritten version.  Note that this works only for
+# commits seen by the current git-filter-branch run!  If you need to
+# correct "dead" links from a previous run, then supply the rewrite
+# file.
+#
+# You don't need the tournament filter if all of the commits IDs you
+# are changing to are "ancient" history, or if you are rewriting
+# history that is a straight line.
+
+no strict;
+no warnings;
+
+use constant REV_MIN_LENGTH => $ENV{MSG_LINK_MIN} || 12;
+
+BEGIN {
+	$mlr = "$ENV{GITDIR}/msg-link-rewrite";
+	if ( -e $mlr ) {
+		open FH, "<", $mlr or die $!;
+		local($/);
+		%remap = m{([0-9a-f]{40})}g while <FH>;
+	}
+	( -d "../map" ) or die "can't see the git-filter-branch map dir";
+}
+
+my $old = $_;
+s{([0-9a-f]{${\(REV_MIN_LENGTH)},40})}{substr(($remap{$1}?do {
+	print STDERR " Remapped: $1 => $remap{$1} (rule)\n";
+	$remap{$1}; }
+		   : $1), 0, length($1))}eg;
+
+s{([0-9a-f]{${\(REV_MIN_LENGTH)},40})}{substr((-e "../map/$1" ? do {
+	$x = `cat ../map/$1`; chomp($x);
+		print STDERR " Remapped: $1 => $x (ripple)\n";
+	$x}
+: $1),0,length($1))}eg;
+
+if ( $old ne $_ ) {
+	print STDERR "*** filtered: pre: \n$old\n post:\n$_\n";
+}
+
+print;
diff --git a/contrib/filter-branch/tournament-links.perl b/contrib/filter-branch/tournament-links.perl
new file mode 100755
index 0000000..479a28f
--- /dev/null
+++ b/contrib/filter-branch/tournament-links.perl
@@ -0,0 +1,148 @@
+#!/usr/bin/perl
+#
+#  tournament-links.perl: a tournament that allows simple re-ordering.
+#
+#  This provides the requirement of getting the commits in the right
+#  order if there are any (full length) commitids in the commit
+#  messages.  It also provides for some remapping along the way (if,
+#  say, a referenced commit ID was wrong).  To do that, put it in a
+#  file in GIT_DIR called "msg-link-rewrite".
+#
+#  The format of msg-link-rewrite is very simple - any full-length
+#  SHA1s that are seen in the file are put into a single hash.  So,
+#  every other item is a key, starting with the first.  Everything
+#  else in the file is ignored.  This is combined with the remapping
+#  that the current git-filter-branch run has performed.
+#
+#  This script is only called once, at the beginning of the run, and
+#  can be relatively expensive on this basis.  This one is practically
+#  guaranteed to feed quicksort it's worst nightmare every time.
+#
+#  Example:
+#    $ cat > .git/msg-link-rewrite <<EOF
+#        Rewrite the commit ID 123456789012 to
+#              '92712632981923'
+#    EOF
+#    $ path=$ENV{HOME}/src/git/contrib/filter-branch
+#    $ git-filter-branch --tournament $path/tournament.perl \
+#         --msg-filter $path/msg-links.perl
+#
+
+no strict;
+no warnings;
+# use sort '_mergesort'; # Perl 5.10+ - use a mergesort instead
+
+use constant WATCH_BATTLE => $ENV{WATCH_BATTLE};
+use constant TOURNAMENT_RESULTS => $ENV{TOURNAMENT_RESULTS};
+use constant REV_MIN_LENGTH => $ENV{MSG_LINK_MIN} || 12;
+
+BEGIN {
+	$mlr = "$ENV{GITDIR}/msg-link-rewrite";
+	if ( -e $mlr ) {
+		open FH, "<", $mlr or die $!;
+		local($/);
+		%remap = m{([0-9a-f]{${\(REV_MIN_LENGTH)},40})}g while <FH>;
+	}
+	( -d "../map" ) or die "can't see the git-filter-branch map dir";
+}
+
+@commits = <>;
+%refers;
+
+# shorten a commit ID to something human-readable for display to
+# STDERR only
+sub n{
+	my $commitid = shift;
+	substr($commitid, 0, 12);
+}
+
+print STDERR "tournament: read ".@commits." commit IDs\n"
+	if TOURNAMENT_RESULTS;
+
+for my $line ( @commits ) {
+	chomp($line);
+	my ($commit, @parents) = split " ", $line;
+	my @refers_commitids;
+	open COMMIT, "-|", qw(git cat-file commit), $commit;
+	while ( <COMMIT> ) {
+		push @refers_commitids, $1 if m{^parent (.*)$};
+		last if m{^\s*$};
+	}
+
+	my $msg = join "", <COMMIT>;
+
+	my @refers_commitids = map {
+		my ($match) = $remap{$_} ? ($remap{$_})
+			: map { $remap{$_} } grep m{^$_}, keys %remap;
+		if ( $match ) {
+			print STDERR "msg-link in ".n($commit).": "
+				.n($_)." treated as ".n($match)."\n"
+					if WATCH_BATTLE;
+			$match;
+		}
+		elsif ( do { $match = `git rev-parse $_`; !$? } ) {
+			chomp($match);
+			print STDERR "msg-link seen in ".n($commit)
+				." to ".n($match)."\n"
+					if WATCH_BATTLE;
+			$match;
+		}
+		else {
+			print STDERR "dead msg-link seen in ".n($commit)
+				." to ".n($_)."\n";
+			$_;
+		}
+	} ($msg =~ m{([0-9a-f]{${\(REV_MIN_LENGTH)},40})}g);
+
+	warn("$commit is not an extant revision\n"), next if $?;
+
+	$refers{$commit} = { map { $_ => 1 } @refers_commitids, @parents };
+}
+print STDERR "tournament: read ".@commits." commits\n"
+	if TOURNAMENT_RESULTS;
+
+my $O;
+
+# returns 1 if source refers to target, or target is a parent of
+# anything referred to by source.
+my $refers = sub {
+	my $source = shift;
+	my $target = shift;
+	my @todo = keys %{ $refers{$source}||{} };
+	my %seen = map { $_ => 1 } @todo;
+	while ( my $commit = shift @todo ) {
+		$O++;
+		do {
+			print STDERR n($source)." refers to ".n($target)."\n"
+				if WATCH_BATTLE;
+			return 1;
+		} if $commit eq $target;
+		push @todo, grep { $O++; !$seen{$_}++ }
+			keys %{ $refers{$commit} || {} };
+	}
+	print STDERR n($source)." does not refer to ".n($target)."\n"
+		if WATCH_BATTLE;
+	return 0;
+};
+
+# It's tournament time!
+print "$_\n" for sort {
+	(my $_a = $a) =~ s{ .*}{};
+	(my $_b = $b) =~ s{ .*}{};
+	my $cmp_val = 0;
+	if ( $refers->($_a, $_b) ) {
+		++$cmp_val;
+	}
+	elsif ( $refers->($_b, $_a) ) {
+		--$cmp_val;
+	}
+	print STDERR n($a)." ".
+		(($cmp_val == 0 ? "EQUIVALENT TO" :
+		  $cmp_val == -1 ? "COMES BEFORE" :
+		  $cmp_val == 1 ? "COMES AFTER" : "TSNH")
+		 ." ".n($b)."\n")
+			if WATCH_BATTLE;
+	$cmp_val;
+} @commits;
+
+print STDERR "tournament-links.perl: O(".@commits.") = ".$O."\n";
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index ae29f47..6c099ea 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -110,6 +110,7 @@ filter_msg=cat
 filter_commit='git commit-tree "$@"'
 filter_tag_name=
 filter_subdir=
+tournament=
 orig_namespace=refs/original/
 force=
 while :
@@ -166,6 +167,9 @@ do
 	--subdirectory-filter)
 		filter_subdir="$OPTARG"
 		;;
+	--tournament)
+		tournament="$OPTARG"
+		;;
 	--original)
 		orig_namespace=$(expr "$OPTARG/" : '\(.*[^/]\)/*$')/
 		;;
@@ -254,6 +258,13 @@ commits=$(wc -l <../revs | tr -d " ")
 
 test $commits -eq 0 && die "Found nothing to rewrite"
 
+if [ -n "$tournament" ]
+then
+	cat ../revs | (eval "$tournament") > ../revs.sorted
+	mv ../revs ../revs.pre-sorted
+	mv ../revs.sorted ../revs
+fi
+
 # Rewrite the commits
 
 i=0
diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
index 5f60b22..8d5679d 100755
--- a/t/t7003-filter-branch.sh
+++ b/t/t7003-filter-branch.sh
@@ -165,4 +165,25 @@ test_expect_success '"map" works in commit filter' '
 	git rev-parse --verify master
 '
 
+test_expect_success "Specifying a custom tournament" '
+        git checkout -b custom-tournament &&
+	echo "Hello">&2
+        echo j > j &&
+	git add j &&
+	echo "There">&2
+        git commit -m j j &&
+        echo z > z &&
+	git add z &&
+	echo "Dude">&2
+        git commit -m z &&
+        echo x > x &&
+	git add x &&
+	echo "Dude">&2
+        git commit -m x &&
+        git-filter-branch -f --parent-filter cat --tournament "cat > foo; fp=\$(head -1 foo | awk \"{print \\\$2}\"); ( head -2 foo | tac; tail -1 foo ) | awk \"{print \\\$1}\" | while read x y; do echo \$x \$fp; fp=\$x; done"  master..HEAD &&
+	order=$(git-log --pretty=format:"%s" master...custom-tournament | xargs) &&
+	echo "order is <$order>" &&
+	test "$order" = "x j z"
+'
+
 test_done
-- 
1.5.3.5
Linus Torvalds· Jan 4, 2008, 18:34 UTC · re: Sam Vilain · lore

Re: Retroactively change email signature? [resend]

On Thu, 3 Jan 2008, Sam Vilain wrote:
> 
> Assuming, of course, that the commit you refer to in a message like that
> is in your history.  If it's referring to another commit in a different
> branch, which may be being re-written, you've got two problems:

I don't care. If some commit refers to another commit that isn't its ancestor, then that is undefined *anyway*.

So the only case that really matters is when you refer to your own strict ancestor.

This is one reason why it was so crazy that "git cherry-pick" used to have "-x" by default - appending the original SHA1 (which is generally *not* an ancestor) is meaningless and stupid.

But in the case of "git revert", it should be an ancestor (or the _user_ is just insane, in which case it doesn't matter - insane people can do insane things)

		Linus
Sam Vilain· Jan 4, 2008, 23:04 UTC · re: Linus Torvalds · lore

Re: Retroactively change email signature? [resend]

Linus Torvalds wrote:
Show 9 quoted lines
> I don't care. If some commit refers to another commit that isn't its 
> ancestor, then that is undefined *anyway*. 
> 
> So the only case that really matters is when you refer to your own strict 
> ancestor.
> 
> This is one reason why it was so crazy that "git cherry-pick" used to have 
> "-x" by default - appending the original SHA1 (which is generally *not* an 
> ancestor) is meaningless and stupid.

Not stupid at all. It gives you an easily clickable link to the previous version of the patch, which may be of interest, and is otherwise irretrievable information if the patch needed resolving.

Sam.
Sam Vilain· Jan 4, 2008, 23:08 UTC · re: Sam Vilain · lore

Re: Retroactively change email signature? [resend]

Sam Vilain wrote:
Show 14 quoted lines
> Linus Torvalds wrote:
>> I don't care. If some commit refers to another commit that isn't its 
>> ancestor, then that is undefined *anyway*. 
>>
>> So the only case that really matters is when you refer to your own strict 
>> ancestor.
>>
>> This is one reason why it was so crazy that "git cherry-pick" used to have 
>> "-x" by default - appending the original SHA1 (which is generally *not* an 
>> ancestor) is meaningless and stupid.
> 
> Not stupid at all.  It gives you an easily clickable link to the
> previous version of the patch, which may be of interest, and is
> otherwise irretrievable information if the patch needed resolving.

footnote: of course it is entirely meaningless if the referred SHA1 is not published anywhere, so I agree with the cherry-pick default setting.

Sam.

← back to recent threads