From: Sam Vilain <sam@vilain.net>
To: Junio Hamano <gitster@pobox.com>
Cc: git@vger.kernel.org
Subject: Re: Retroactively change email signature? [resend]
Date: Thu, 03 Jan 2008 18:52:55 +1300 [thread overview]
Message-ID: <477C7837.9000303@vilain.net> (raw)
In-Reply-To: <alpine.LFD.1.00.0801021316080.3010@woody.linux-foundation.org>
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
next prev parent reply other threads:[~2008-01-04 13:02 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2008-01-02 20:37 Retroactively change email signature? Stephen Sinclair
2008-01-02 21:17 ` Jakub Narebski
2008-01-02 21:24 ` Linus Torvalds
2008-01-02 22:06 ` Stephen Sinclair
2008-01-03 0:53 ` David Brown
2008-01-03 5:52 ` Sam Vilain [this message]
2008-01-04 18:34 ` Retroactively change email signature? [resend] Linus Torvalds
2008-01-04 23:04 ` Sam Vilain
2008-01-04 23:08 ` Sam Vilain
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=477C7837.9000303@vilain.net \
--to=sam@vilain.net \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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).