diff --git a/combine-diff.c b/combine-diff.c index 6dc06093d3..3d2aaf34ab 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -10,6 +10,7 @@ #include "refs.h" #include "userdiff.h" #include "sha1-array.h" +#include "revision.h" static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent) { @@ -1383,7 +1384,7 @@ void diff_tree_combined(const unsigned char *sha1, void diff_tree_combined_merge(const struct commit *commit, int dense, struct rev_info *rev) { - struct commit_list *parent = commit->parents; + struct commit_list *parent = get_saved_parents(rev, commit); struct sha1_array parents = SHA1_ARRAY_INIT; while (parent) { diff --git a/commit.c b/commit.c index e5862f6d7c..5ecdb38b3e 100644 --- a/commit.c +++ b/commit.c @@ -377,6 +377,22 @@ unsigned commit_list_count(const struct commit_list *l) return c; } +struct commit_list *copy_commit_list(struct commit_list *list) +{ + struct commit_list *head = NULL; + struct commit_list **pp = &head; + while (list) { + struct commit_list *new; + new = xmalloc(sizeof(struct commit_list)); + new->item = list->item; + new->next = NULL; + *pp = new; + pp = &new->next; + list = list->next; + } + return head; +} + void free_commit_list(struct commit_list *list) { while (list) { diff --git a/commit.h b/commit.h index 35cc4e266b..ca766d2f4a 100644 --- a/commit.h +++ b/commit.h @@ -62,6 +62,9 @@ struct commit_list *commit_list_insert_by_date(struct commit *item, struct commit_list **list); void commit_list_sort_by_date(struct commit_list **list); +/* Shallow copy of the input list */ +struct commit_list *copy_commit_list(struct commit_list *list); + void free_commit_list(struct commit_list *list); /* Commit formats */ diff --git a/log-tree.c b/log-tree.c index a49d8e895d..8534d91826 100644 --- a/log-tree.c +++ b/log-tree.c @@ -738,7 +738,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log sha1 = commit->tree->object.sha1; /* Root commit? */ - parents = commit->parents; + parents = get_saved_parents(opt, commit); if (!parents) { if (opt->show_root_diff) { diff_root_tree_sha1(sha1, "", &opt->diffopt); 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); +} diff --git a/revision.h b/revision.h index 95859ba119..e7f1d211bf 100644 --- a/revision.h +++ b/revision.h @@ -25,6 +25,7 @@ struct rev_info; struct log_info; struct string_list; +struct saved_parents; struct rev_cmdline_info { unsigned int nr; @@ -187,6 +188,9 @@ struct rev_info { /* line level range that we are chasing */ struct decoration line_log_data; + + /* copies of the parent lists, for --full-diff display */ + struct saved_parents *saved_parents_slab; }; #define REV_TREE_SAME 0 @@ -273,4 +277,20 @@ typedef enum rewrite_result (*rewrite_parent_fn_t)(struct rev_info *revs, struct extern int rewrite_parents(struct rev_info *revs, struct commit *commit, rewrite_parent_fn_t rewrite_parent); + +/* + * Save a copy of the parent list, and return the saved copy. This is + * used by the log machinery to retrieve the original parents when + * commit->parents has been modified by history simpification. + * + * You may only call save_parents() once per commit (this is checked + * for non-root commits). + * + * get_saved_parents() will transparently return commit->parents if + * history simplification is off. + */ +extern void save_parents(struct rev_info *revs, struct commit *commit); +extern struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit); +extern void free_saved_parents(struct rev_info *revs); + #endif diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index 57ce2395d6..fde5e712eb 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -127,4 +127,10 @@ test_expect_success 'full history simplification without parent' ' } ' +test_expect_success '--full-diff is not affected by --parents' ' + git log -p --pretty="%H" --full-diff -- file >expected && + git log -p --pretty="%H" --full-diff --parents -- file >actual && + test_cmp expected actual +' + test_done