unpack_trees(): skip trees that are the same in all input

unpack_trees() merges two trees (the current HEAD and the destination
commit) when switching to another branch, checking and updating the index
entry where the destination differs from the current HEAD.  It merges three
trees (the common ancestor, the current HEAD and the other commit) when
performing a three-way merge, checking and updating the index entry when
the merge result differs from the current HEAD.  It does so by walking the
input trees in parallel all the way down to the leaves.

One common special case is a directory is identical across the trees
involved in the merge.  In such a case, we do not have to descend into the
directory at all---we know that the end result is to keep the entries in
the current index.

This optimization cannot be applied in a few special cases in
unpack_trees(), though.  We need to descend into the directory and update
the index entries from the target tree in the following cases:

 - When resetting (e.g. "git reset --hard"); and

 - When checking out a tree for the first time into an empty working tree
   (e.g. "git read-tree -m -u HEAD HEAD" with missing .git/index).

Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Junio C Hamano 2010-12-22 14:13:47 -08:00
Родитель 84563a624e
Коммит 83c90314aa
1 изменённых файлов: 56 добавлений и 0 удалений

Просмотреть файл

@ -436,6 +436,10 @@ static int switch_cache_bottom(struct traverse_info *info)
return ret; return ret;
} }
static int fast_forward_merge(int n, unsigned long dirmask,
struct name_entry *names,
struct traverse_info *info);
static int traverse_trees_recursive(int n, unsigned long dirmask, static int traverse_trees_recursive(int n, unsigned long dirmask,
unsigned long df_conflicts, unsigned long df_conflicts,
struct name_entry *names, struct name_entry *names,
@ -447,6 +451,11 @@ static int traverse_trees_recursive(int n, unsigned long dirmask,
struct traverse_info newinfo; struct traverse_info newinfo;
struct name_entry *p; struct name_entry *p;
if (!df_conflicts) {
int status = fast_forward_merge(n, dirmask, names, info);
if (status)
return status;
}
p = names; p = names;
while (!p->mode) while (!p->mode)
p++; p++;
@ -694,6 +703,53 @@ static struct cache_entry *find_cache_entry(struct traverse_info *info,
return NULL; return NULL;
} }
static int fast_forward_merge(int n, unsigned long dirmask,
struct name_entry *names,
struct traverse_info *info)
{
int i;
struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
struct unpack_trees_options *o = info->data;
/* merging two or more trees with an identical subdirectory? */
if ((n < 2) || ((1UL << n) - 1) != dirmask ||
!o->merge || o->reset || o->initial_checkout)
return 0;
for (i = 1; i < n; i++)
if (hashcmp(names[i-1].sha1, names[i].sha1))
return 0;
/*
* Instead of descending into the directory, keep the contents
* of the current index.
*/
while (1) {
struct cache_entry *ce;
ce = next_cache_entry(o);
if (!ce)
break;
/* Is the entry still in that directory? */
if (do_compare_entry(ce, info, names))
break;
/*
* Note: we do not just run unpack_index_entry() here,
* as the callback may want to compare what is in the
* index with what are from the HEAD and the other tree
* and reject the merge. We pretend that ancestors, the
* HEAD and the other tree all have the same contents as
* the current index, which is a lie, but it works.
*/
for (i = 0; i < n + 1; i++)
src[i] = ce;
mark_ce_used(ce, o);
if (call_unpack_fn(src, o) < 0)
return unpack_failed(o, NULL);
if (ce_stage(ce))
mark_ce_used_same_name(ce, o);
}
return dirmask;
}
static void debug_path(struct traverse_info *info) static void debug_path(struct traverse_info *info)
{ {
if (info->prev) { if (info->prev) {