summaryrefslogtreecommitdiff
path: root/revision.c
diff options
context:
space:
mode:
authorThomas Rast <trast@inf.ethz.ch>2013-07-31 22:13:20 +0200
committerJunio C Hamano <gitster@pobox.com>2013-08-01 10:25:48 -0700
commit53d00b39ce00073ab6b450488acc3f532a223e8f (patch)
treed69153356dd4c154123b6724a660ce99974c579f /revision.c
parent0bde8c0c1e53e2b2001f4ced58d3e66865758cea (diff)
downloadgit-53d00b39ce00073ab6b450488acc3f532a223e8f.tar.gz
log: use true parents for diff even when rewriting
When using pathspec filtering in combination with diff-based log output, parent simplification happens before the diff is computed. The diff is therefore against the *simplified* parents. This works okay, arguably by accident, in the normal case: simplification reduces to one parent as long as the commit is TREESAME to it. So the simplified parent of any given commit must have the same tree contents on the filtered paths as its true (unfiltered) parent. However, --full-diff breaks this guarantee, and indeed gives pretty spectacular results when comparing the output of git log --graph --stat ... git log --graph --full-diff --stat ... (--graph internally kicks in parent simplification, much like --parents). To fix it, store a copy of the parent list before simplification (in a slab) whenever --full-diff is in effect. Then use the stored parents instead of the simplified ones in the commit display code paths. The latter do not actually check for --full-diff to avoid duplicated code; they just grab the original parents if save_parents() has not been called for this revision walk. For ordinary commits it should be obvious that this is the right thing to do. Merge commits are a bit subtle. Observe that with default simplification, merge simplification is an all-or-nothing decision: either the merge is TREESAME to one parent and disappears, or it is different from all parents and the parent list remains intact. Redundant parents are not pruned, so the existing code also shows them as a merge. So if we do show a merge commit, the parent list just consists of the rewrite result on each parent. Running, e.g., --cc on this in --full-diff mode is not very useful: if any commits were skipped, some hunks will disagree with all sides of the merge (with one side, because commits were skipped; with the others, because they didn't have those changes in the first place). This triggers --cc showing these hunks spuriously. Therefore I believe that even for merge commits it is better to show the diffs wrt. the original parents. Reported-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk> Signed-off-by: Thomas Rast <trast@inf.ethz.ch> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'revision.c')
-rw-r--r--revision.c43
1 files changed, 42 insertions, 1 deletions
diff --git a/revision.c b/revision.c
index 84ccc0529b..e3ca9361b4 100644
--- a/revision.c
+++ b/revision.c
@@ -15,6 +15,7 @@
#include "string-list.h"
#include "line-log.h"
#include "mailmap.h"
+#include "commit-slab.h"
volatile show_early_output_fn_t show_early_output;
@@ -2763,7 +2764,7 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
return retval;
}
-static inline int want_ancestry(struct rev_info *revs)
+static inline int want_ancestry(const struct rev_info *revs)
{
return (revs->rewrite_parents || revs->children.name);
}
@@ -2820,6 +2821,14 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
if (action == commit_show &&
!revs->show_all &&
revs->prune && revs->dense && want_ancestry(revs)) {
+ /*
+ * --full-diff on simplified parents is no good: it
+ * will show spurious changes from the commits that
+ * were elided. So we save the parents on the side
+ * when --full-diff is in effect.
+ */
+ if (revs->full_diff)
+ save_parents(revs, commit);
if (rewrite_parents(revs, commit, rewrite_one) < 0)
return commit_error;
}
@@ -3038,6 +3047,8 @@ struct commit *get_revision(struct rev_info *revs)
c = get_revision_internal(revs);
if (c && revs->graph)
graph_update(revs->graph, c);
+ if (!c)
+ free_saved_parents(revs);
return c;
}
@@ -3069,3 +3080,33 @@ void put_revision_mark(const struct rev_info *revs, const struct commit *commit)
fputs(mark, stdout);
putchar(' ');
}
+
+define_commit_slab(saved_parents, struct commit_list *);
+
+void save_parents(struct rev_info *revs, struct commit *commit)
+{
+ struct commit_list **pp;
+
+ if (!revs->saved_parents_slab) {
+ revs->saved_parents_slab = xmalloc(sizeof(struct saved_parents));
+ init_saved_parents(revs->saved_parents_slab);
+ }
+
+ pp = saved_parents_at(revs->saved_parents_slab, commit);
+ assert(*pp == NULL);
+ *pp = copy_commit_list(commit->parents);
+}
+
+struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit)
+{
+ if (!revs->saved_parents_slab)
+ return commit->parents;
+
+ return *saved_parents_at(revs->saved_parents_slab, commit);
+}
+
+void free_saved_parents(struct rev_info *revs)
+{
+ if (revs->saved_parents_slab)
+ clear_saved_parents(revs->saved_parents_slab);
+}