Code

filter-branch: fix ref rewriting with --subdirectory-filter
authorThomas Rast <trast@student.ethz.ch>
Tue, 12 Aug 2008 08:45:58 +0000 (10:45 +0200)
committerJunio C Hamano <gitster@pobox.com>
Wed, 13 Aug 2008 00:27:17 +0000 (17:27 -0700)
The previous ancestor discovery code failed on any refs that are
(pre-rewrite) ancestors of commits marked for rewriting.  This means
that in a situation

   A -- B(topic) -- C(master)

where B is dropped by --subdirectory-filter pruning, the 'topic' was
not moved up to A as intended, but left unrewritten because we asked
about 'git rev-list ^master topic', which does not return anything.

Instead, we use the straightforward

   git rev-list -1 $ref -- $filter_subdir

to find the right ancestor.  To justify this, note that the nearest
ancestor is unique: We use the output of

  git rev-list --parents -- $filter_subdir

to rewrite commits in the first pass, before any ref rewriting.  If B
is a non-merge commit, the only candidate is its parent.  If it is a
merge, there are two cases:

- All sides of the merge bring the same subdirectory contents.  Then
  rev-list already pruned away the merge in favour for just one of its
  parents, so there is only one candidate.

- Some merge sides, or the merge outcome, differ.  Then the merge is
  not pruned and can be rewritten directly.

So it is always safe to use rev-list -1.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
git-filter-branch.sh
t/t7003-filter-branch.sh

index a324cf0596ee0f05831190ce724fe9134bc7f568..a140337e3558888f625f9656f7c21ea25f64d8be 100755 (executable)
@@ -317,24 +317,19 @@ done <../revs
 
 # In case of a subdirectory filter, it is possible that a specified head
 # is not in the set of rewritten commits, because it was pruned by the
-# revision walker.  Fix it by mapping these heads to the next rewritten
-# ancestor(s), i.e. the boundaries in the set of rewritten commits.
+# revision walker.  Fix it by mapping these heads to the unique nearest
+# ancestor that survived the pruning.
 
-# NEEDSWORK: we should sort the unmapped refs topologically first
-while read ref
-do
-       sha1=$(git rev-parse "$ref"^0)
-       test -f "$workdir"/../map/$sha1 && continue
-       # Assign the boundarie(s) in the set of rewritten commits
-       # as the replacement commit(s).
-       # (This would look a bit nicer if --not --stdin worked.)
-       for p in $( (cd "$workdir"/../map; ls | sed "s/^/^/") |
-               git rev-list $ref --boundary --stdin |
-               sed -n "s/^-//p")
+if test "$filter_subdir"
+then
+       while read ref
        do
-               map $p >> "$workdir"/../map/$sha1
-       done
-done < "$tempdir"/heads
+               sha1=$(git rev-parse "$ref"^0)
+               test -f "$workdir"/../map/$sha1 && continue
+               ancestor=$(git rev-list -1 $ref -- "$filter_subdir")
+               test "$ancestor" && echo $(map $ancestor) >> "$workdir"/../map/$sha1
+       done < "$tempdir"/heads
+fi
 
 # Finally update the refs
 
index 4382baa27dae7c9ba9d51551dbfbb6efb74e91bd..233254f2b541b7c67d99aa738eb55f0a0a6d3131 100755 (executable)
@@ -101,7 +101,7 @@ test_expect_success 'filter subdirectory only' '
                refs/heads/sub refs/heads/sub-earlier
 '
 
-test_expect_failure 'subdirectory filter result looks okay' '
+test_expect_success 'subdirectory filter result looks okay' '
        test 2 = $(git rev-list sub | wc -l) &&
        git show sub:new &&
        test_must_fail git show sub:subdir &&