Merge branch 'tr/log-full-diff-keep-true-parents'

Output from "git log --full-diff -- <pathspec>" looked strange,
because comparison was done with the previous ancestor that touched
the specified <pathspec>, causing the patches for paths outside the
pathspec to show more than the single commit has changed.

Tweak "git reflog -p" for the same reason using the same mechanism.

* tr/log-full-diff-keep-true-parents:
  log: use true parents for diff when walking reflogs
  log: use true parents for diff even when rewriting
This commit is contained in:
Junio C Hamano 2013-09-09 14:33:16 -07:00
commit 4ab4a6dfb4
8 changed files with 134 additions and 3 deletions

View File

@ -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) {

View File

@ -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) {

View File

@ -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 */

View File

@ -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);

View File

@ -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;
}
@ -2839,6 +2848,7 @@ static struct commit *get_revision_1(struct rev_info *revs)
free(entry);
if (revs->reflog_info) {
save_parents(revs, commit);
fake_reflog_parent(revs->reflog_info, commit);
commit->object.flags &= ~(ADDED | SEEN | SHOWN);
}
@ -3038,6 +3048,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 +3081,54 @@ 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 *);
#define EMPTY_PARENT_LIST ((struct commit_list *)-1)
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);
/*
* When walking with reflogs, we may visit the same commit
* several times: once for each appearance in the reflog.
*
* In this case, save_parents() will be called multiple times.
* We want to keep only the first set of parents. We need to
* store a sentinel value for an empty (i.e., NULL) parent
* list to distinguish it from a not-yet-saved list, however.
*/
if (*pp)
return;
if (commit->parents)
*pp = copy_commit_list(commit->parents);
else
*pp = EMPTY_PARENT_LIST;
}
struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit)
{
struct commit_list *parents;
if (!revs->saved_parents_slab)
return commit->parents;
parents = *saved_parents_at(revs->saved_parents_slab, commit);
if (parents == EMPTY_PARENT_LIST)
return NULL;
return parents;
}
void free_saved_parents(struct rev_info *revs)
{
if (revs->saved_parents_slab)
clear_saved_parents(revs->saved_parents_slab);
}

View File

@ -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

View File

@ -144,4 +144,26 @@ test_expect_success 'empty reflog file' '
test_cmp expect actual
'
# This guards against the alternative of showing the diffs vs. the
# reflog ancestor. The reflog used is designed to list the commits
# more than once, so as to exercise the corresponding logic.
test_expect_success 'git log -g -p shows diffs vs. parents' '
test_commit two &&
git branch flipflop &&
git update-ref refs/heads/flipflop -m flip1 HEAD^ &&
git update-ref refs/heads/flipflop -m flop1 HEAD &&
git update-ref refs/heads/flipflop -m flip2 HEAD^ &&
git log -g -p flipflop >reflog &&
grep -v ^Reflog reflog >actual &&
git log -1 -p HEAD^ >log.one &&
git log -1 -p HEAD >log.two &&
(
cat log.one; echo
cat log.two; echo
cat log.one; echo
cat log.two
) >expect &&
test_cmp expect actual
'
test_done

View File

@ -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