unpack-trees.c: prepare for looking ahead in the index
This prepares but does not yet implement a look-ahead in the index entries when traverse-trees.c decides to give us tree entries in an order that does not match what is in the index. A case where a look-ahead in the index is necessary happens when merging branch B into branch A while the index matches the current branch A, using a tree O as their common ancestor, and these three trees looks like this: O A B t t t-i t-i t-i t-j t-j t/1 t/2 The traverse_trees() function gets "t", "t-i" and "t" from trees O, A and B first, and notices that A may have a matching "t" behind "t-i" and "t-j" (indeed it does), and tells A to give that entry instead. After unpacking blob "t" from tree B (as it hasn't changed since O in B and A removed it, it will result in its removal), it descends into directory "t/". The side that walked index in parallel to the tree traversal used to be implemented with one pointer, o->pos, that points at the next index entry to be processed. When this happens, the pointer o->pos still points at "t-i" that is the first entry. We should be able to skip "t-i" and "t-j" and locate "t/1" from the index while the recursive invocation of traverse_trees() walks and match entries found there, and later come back to process "t-i". While that look-ahead is not implemented yet, this adds a flag bit, CE_UNPACKED, to mark the entries in the index that has already been processed. o->pos pointer has been renamed to o->cache_bottom and it points at the first entry that may still need to be processed. Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
parent
cee2d6ae63
commit
da165f470e
2
cache.h
2
cache.h
|
@ -178,6 +178,8 @@ struct cache_entry {
|
||||||
#define CE_HASHED (0x100000)
|
#define CE_HASHED (0x100000)
|
||||||
#define CE_UNHASHED (0x200000)
|
#define CE_UNHASHED (0x200000)
|
||||||
|
|
||||||
|
#define CE_UNPACKED (0x1000000)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Extended on-disk flags
|
* Extended on-disk flags
|
||||||
*/
|
*/
|
||||||
|
|
18
diff-lib.c
18
diff-lib.c
|
@ -359,21 +359,6 @@ static void do_oneway_diff(struct unpack_trees_options *o,
|
||||||
show_modified(revs, tree, idx, 1, cached, match_missing);
|
show_modified(revs, tree, idx, 1, cached, match_missing);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_options *o)
|
|
||||||
{
|
|
||||||
int len = ce_namelen(ce);
|
|
||||||
const struct index_state *index = o->src_index;
|
|
||||||
|
|
||||||
while (o->pos < index->cache_nr) {
|
|
||||||
struct cache_entry *next = index->cache[o->pos];
|
|
||||||
if (len != ce_namelen(next))
|
|
||||||
break;
|
|
||||||
if (memcmp(ce->name, next->name, len))
|
|
||||||
break;
|
|
||||||
o->pos++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The unpack_trees() interface is designed for merging, so
|
* The unpack_trees() interface is designed for merging, so
|
||||||
* the different source entries are designed primarily for
|
* the different source entries are designed primarily for
|
||||||
|
@ -395,9 +380,6 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
|
||||||
struct cache_entry *tree = src[1];
|
struct cache_entry *tree = src[1];
|
||||||
struct rev_info *revs = o->unpack_data;
|
struct rev_info *revs = o->unpack_data;
|
||||||
|
|
||||||
if (idx && ce_stage(idx))
|
|
||||||
skip_same_name(idx, o);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Unpack-trees generates a DF/conflict entry if
|
* Unpack-trees generates a DF/conflict entry if
|
||||||
* there was a directory in the index and a tree
|
* there was a directory in the index and a tree
|
||||||
|
|
216
unpack-trees.c
216
unpack-trees.c
|
@ -126,18 +126,109 @@ static inline int call_unpack_fn(struct cache_entry **src, struct unpack_trees_o
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int unpack_index_entry(struct cache_entry *ce, struct unpack_trees_options *o)
|
static void mark_ce_used(struct cache_entry *ce, struct unpack_trees_options *o)
|
||||||
|
{
|
||||||
|
ce->ce_flags |= CE_UNPACKED;
|
||||||
|
|
||||||
|
if (o->cache_bottom < o->src_index->cache_nr &&
|
||||||
|
o->src_index->cache[o->cache_bottom] == ce) {
|
||||||
|
int bottom = o->cache_bottom;
|
||||||
|
while (bottom < o->src_index->cache_nr &&
|
||||||
|
o->src_index->cache[bottom]->ce_flags & CE_UNPACKED)
|
||||||
|
bottom++;
|
||||||
|
o->cache_bottom = bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mark_all_ce_unused(struct index_state *index)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < index->cache_nr; i++)
|
||||||
|
index->cache[i]->ce_flags &= ~CE_UNPACKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int locate_in_src_index(struct cache_entry *ce,
|
||||||
|
struct unpack_trees_options *o)
|
||||||
|
{
|
||||||
|
struct index_state *index = o->src_index;
|
||||||
|
int len = ce_namelen(ce);
|
||||||
|
int pos = index_name_pos(index, ce->name, len);
|
||||||
|
if (pos < 0)
|
||||||
|
pos = -1 - pos;
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We call unpack_index_entry() with an unmerged cache entry
|
||||||
|
* only in diff-index, and it wants a single callback. Skip
|
||||||
|
* the other unmerged entry with the same name.
|
||||||
|
*/
|
||||||
|
static void mark_ce_used_same_name(struct cache_entry *ce,
|
||||||
|
struct unpack_trees_options *o)
|
||||||
|
{
|
||||||
|
struct index_state *index = o->src_index;
|
||||||
|
int len = ce_namelen(ce);
|
||||||
|
int pos;
|
||||||
|
|
||||||
|
for (pos = locate_in_src_index(ce, o); pos < index->cache_nr; pos++) {
|
||||||
|
struct cache_entry *next = index->cache[pos];
|
||||||
|
if (len != ce_namelen(next) ||
|
||||||
|
memcmp(ce->name, next->name, len))
|
||||||
|
break;
|
||||||
|
mark_ce_used(next, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct cache_entry *next_cache_entry(struct unpack_trees_options *o)
|
||||||
|
{
|
||||||
|
const struct index_state *index = o->src_index;
|
||||||
|
int pos = o->cache_bottom;
|
||||||
|
|
||||||
|
while (pos < index->cache_nr) {
|
||||||
|
struct cache_entry *ce = index->cache[pos];
|
||||||
|
if (!(ce->ce_flags & CE_UNPACKED))
|
||||||
|
return ce;
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_same_unmerged(struct cache_entry *ce,
|
||||||
|
struct unpack_trees_options *o)
|
||||||
|
{
|
||||||
|
struct index_state *index = o->src_index;
|
||||||
|
int len = ce_namelen(ce);
|
||||||
|
int pos = index_name_pos(index, ce->name, len);
|
||||||
|
|
||||||
|
if (0 <= pos)
|
||||||
|
die("programming error in a caller of mark_ce_used_same_name");
|
||||||
|
for (pos = -pos - 1; pos < index->cache_nr; pos++) {
|
||||||
|
struct cache_entry *next = index->cache[pos];
|
||||||
|
if (len != ce_namelen(next) ||
|
||||||
|
memcmp(ce->name, next->name, len))
|
||||||
|
break;
|
||||||
|
add_entry(o, next, 0, 0);
|
||||||
|
mark_ce_used(next, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int unpack_index_entry(struct cache_entry *ce,
|
||||||
|
struct unpack_trees_options *o)
|
||||||
{
|
{
|
||||||
struct cache_entry *src[5] = { ce, NULL, };
|
struct cache_entry *src[5] = { ce, NULL, };
|
||||||
|
int ret;
|
||||||
|
|
||||||
o->pos++;
|
mark_ce_used(ce, o);
|
||||||
if (ce_stage(ce)) {
|
if (ce_stage(ce)) {
|
||||||
if (o->skip_unmerged) {
|
if (o->skip_unmerged) {
|
||||||
add_entry(o, ce, 0, 0);
|
add_entry(o, ce, 0, 0);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return call_unpack_fn(src, o);
|
ret = call_unpack_fn(src, o);
|
||||||
|
if (ce_stage(ce))
|
||||||
|
mark_ce_used_same_name(ce, o);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info)
|
static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info)
|
||||||
|
@ -212,6 +303,20 @@ static int compare_entry(const struct cache_entry *ce, const struct traverse_inf
|
||||||
return ce_namelen(ce) > traverse_path_len(info, n);
|
return ce_namelen(ce) > traverse_path_len(info, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int ce_in_traverse_path(const struct cache_entry *ce,
|
||||||
|
const struct traverse_info *info)
|
||||||
|
{
|
||||||
|
if (!info->prev)
|
||||||
|
return 1;
|
||||||
|
if (do_compare_entry(ce, info->prev, &info->name))
|
||||||
|
return 0;
|
||||||
|
/*
|
||||||
|
* If ce (blob) is the same name as the path (which is a tree
|
||||||
|
* we will be descending into), it won't be inside it.
|
||||||
|
*/
|
||||||
|
return (info->pathlen < ce_namelen(ce));
|
||||||
|
}
|
||||||
|
|
||||||
static struct cache_entry *create_ce_entry(const struct traverse_info *info, const struct name_entry *n, int stage)
|
static struct cache_entry *create_ce_entry(const struct traverse_info *info, const struct name_entry *n, int stage)
|
||||||
{
|
{
|
||||||
int len = traverse_path_len(info, n);
|
int len = traverse_path_len(info, n);
|
||||||
|
@ -300,23 +405,27 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
|
||||||
|
|
||||||
/* Are we supposed to look at the index too? */
|
/* Are we supposed to look at the index too? */
|
||||||
if (o->merge) {
|
if (o->merge) {
|
||||||
while (o->pos < o->src_index->cache_nr) {
|
while (1) {
|
||||||
struct cache_entry *ce = o->src_index->cache[o->pos];
|
struct cache_entry *ce = next_cache_entry(o);
|
||||||
int cmp = compare_entry(ce, info, p);
|
int cmp;
|
||||||
|
if (!ce)
|
||||||
|
break;
|
||||||
|
cmp = compare_entry(ce, info, p);
|
||||||
if (cmp < 0) {
|
if (cmp < 0) {
|
||||||
if (unpack_index_entry(ce, o) < 0)
|
if (unpack_index_entry(ce, o) < 0)
|
||||||
return unpack_failed(o, NULL);
|
return unpack_failed(o, NULL);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!cmp) {
|
if (!cmp) {
|
||||||
o->pos++;
|
|
||||||
if (ce_stage(ce)) {
|
if (ce_stage(ce)) {
|
||||||
/*
|
/*
|
||||||
* If we skip unmerged index entries, we'll skip this
|
* If we skip unmerged index
|
||||||
* entry *and* the tree entries associated with it!
|
* entries, we'll skip this
|
||||||
|
* entry *and* the tree
|
||||||
|
* entries associated with it!
|
||||||
*/
|
*/
|
||||||
if (o->skip_unmerged) {
|
if (o->skip_unmerged) {
|
||||||
add_entry(o, ce, 0, 0);
|
add_same_unmerged(ce, o);
|
||||||
return mask;
|
return mask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -329,6 +438,13 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
|
||||||
if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0)
|
if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
if (src[0]) {
|
||||||
|
if (ce_stage(src[0]))
|
||||||
|
mark_ce_used_same_name(src[0], o);
|
||||||
|
else
|
||||||
|
mark_ce_used(src[0], o);
|
||||||
|
}
|
||||||
|
|
||||||
/* Now handle any directories.. */
|
/* Now handle any directories.. */
|
||||||
if (dirmask) {
|
if (dirmask) {
|
||||||
unsigned long conflicts = mask & ~dirmask;
|
unsigned long conflicts = mask & ~dirmask;
|
||||||
|
@ -345,11 +461,13 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
|
||||||
matches = cache_tree_matches_traversal(o->src_index->cache_tree,
|
matches = cache_tree_matches_traversal(o->src_index->cache_tree,
|
||||||
names, info);
|
names, info);
|
||||||
/*
|
/*
|
||||||
* Everything under the name matches. Adjust o->pos to
|
* Everything under the name matches; skip the
|
||||||
* skip the entire hierarchy.
|
* entire hierarchy. diff_index_cached codepath
|
||||||
|
* special cases D/F conflicts in such a way that
|
||||||
|
* it does not do any look-ahead, so this is safe.
|
||||||
*/
|
*/
|
||||||
if (matches) {
|
if (matches) {
|
||||||
o->pos += matches;
|
o->cache_bottom += matches;
|
||||||
return mask;
|
return mask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -382,11 +500,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
|
||||||
|
|
||||||
memset(&o->result, 0, sizeof(o->result));
|
memset(&o->result, 0, sizeof(o->result));
|
||||||
o->result.initialized = 1;
|
o->result.initialized = 1;
|
||||||
if (o->src_index) {
|
o->result.timestamp.sec = o->src_index->timestamp.sec;
|
||||||
o->result.timestamp.sec = o->src_index->timestamp.sec;
|
o->result.timestamp.nsec = o->src_index->timestamp.nsec;
|
||||||
o->result.timestamp.nsec = o->src_index->timestamp.nsec;
|
|
||||||
}
|
|
||||||
o->merge_size = len;
|
o->merge_size = len;
|
||||||
|
mark_all_ce_unused(o->src_index);
|
||||||
|
|
||||||
if (!dfc)
|
if (!dfc)
|
||||||
dfc = xcalloc(1, cache_entry_size(0));
|
dfc = xcalloc(1, cache_entry_size(0));
|
||||||
|
@ -400,18 +517,38 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
|
||||||
info.fn = unpack_callback;
|
info.fn = unpack_callback;
|
||||||
info.data = o;
|
info.data = o;
|
||||||
|
|
||||||
|
if (o->prefix) {
|
||||||
|
/*
|
||||||
|
* Unpack existing index entries that sort before the
|
||||||
|
* prefix the tree is spliced into. Note that o->merge
|
||||||
|
* is always true in this case.
|
||||||
|
*/
|
||||||
|
while (1) {
|
||||||
|
struct cache_entry *ce = next_cache_entry(o);
|
||||||
|
if (!ce)
|
||||||
|
break;
|
||||||
|
if (ce_in_traverse_path(ce, &info))
|
||||||
|
break;
|
||||||
|
if (unpack_index_entry(ce, o) < 0)
|
||||||
|
goto return_failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (traverse_trees(len, t, &info) < 0)
|
if (traverse_trees(len, t, &info) < 0)
|
||||||
return unpack_failed(o, NULL);
|
goto return_failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Any left-over entries in the index? */
|
/* Any left-over entries in the index? */
|
||||||
if (o->merge) {
|
if (o->merge) {
|
||||||
while (o->pos < o->src_index->cache_nr) {
|
while (1) {
|
||||||
struct cache_entry *ce = o->src_index->cache[o->pos];
|
struct cache_entry *ce = next_cache_entry(o);
|
||||||
|
if (!ce)
|
||||||
|
break;
|
||||||
if (unpack_index_entry(ce, o) < 0)
|
if (unpack_index_entry(ce, o) < 0)
|
||||||
return unpack_failed(o, NULL);
|
goto return_failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mark_all_ce_unused(o->src_index);
|
||||||
|
|
||||||
if (o->trivial_merges_only && o->nontrivial_merge)
|
if (o->trivial_merges_only && o->nontrivial_merge)
|
||||||
return unpack_failed(o, "Merge requires file-level merging");
|
return unpack_failed(o, "Merge requires file-level merging");
|
||||||
|
@ -421,6 +558,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
|
||||||
if (o->dst_index)
|
if (o->dst_index)
|
||||||
*o->dst_index = o->result;
|
*o->dst_index = o->result;
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
return_failed:
|
||||||
|
mark_all_ce_unused(o->src_index);
|
||||||
|
return unpack_failed(o, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Here come the merge functions */
|
/* Here come the merge functions */
|
||||||
|
@ -522,7 +663,9 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
|
||||||
* in that directory.
|
* in that directory.
|
||||||
*/
|
*/
|
||||||
namelen = strlen(ce->name);
|
namelen = strlen(ce->name);
|
||||||
for (i = o->pos; i < o->src_index->cache_nr; i++) {
|
for (i = locate_in_src_index(ce, o);
|
||||||
|
i < o->src_index->cache_nr;
|
||||||
|
i++) {
|
||||||
struct cache_entry *ce2 = o->src_index->cache[i];
|
struct cache_entry *ce2 = o->src_index->cache[i];
|
||||||
int len = ce_namelen(ce2);
|
int len = ce_namelen(ce2);
|
||||||
if (len < namelen ||
|
if (len < namelen ||
|
||||||
|
@ -530,12 +673,14 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
|
||||||
ce2->name[namelen] != '/')
|
ce2->name[namelen] != '/')
|
||||||
break;
|
break;
|
||||||
/*
|
/*
|
||||||
* ce2->name is an entry in the subdirectory.
|
* ce2->name is an entry in the subdirectory to be
|
||||||
|
* removed.
|
||||||
*/
|
*/
|
||||||
if (!ce_stage(ce2)) {
|
if (!ce_stage(ce2)) {
|
||||||
if (verify_uptodate(ce2, o))
|
if (verify_uptodate(ce2, o))
|
||||||
return -1;
|
return -1;
|
||||||
add_entry(o, ce2, CE_REMOVE, 0);
|
add_entry(o, ce2, CE_REMOVE, 0);
|
||||||
|
mark_ce_used(ce2, o);
|
||||||
}
|
}
|
||||||
cnt++;
|
cnt++;
|
||||||
}
|
}
|
||||||
|
@ -591,7 +736,6 @@ static int verify_absent(struct cache_entry *ce, const char *action,
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (!lstat(ce->name, &st)) {
|
if (!lstat(ce->name, &st)) {
|
||||||
int ret;
|
|
||||||
int dtype = ce_to_dtype(ce);
|
int dtype = ce_to_dtype(ce);
|
||||||
struct cache_entry *result;
|
struct cache_entry *result;
|
||||||
|
|
||||||
|
@ -619,28 +763,8 @@ static int verify_absent(struct cache_entry *ce, const char *action,
|
||||||
* files that are in "foo/" we would lose
|
* files that are in "foo/" we would lose
|
||||||
* them.
|
* them.
|
||||||
*/
|
*/
|
||||||
ret = verify_clean_subdirectory(ce, action, o);
|
if (verify_clean_subdirectory(ce, action, o) < 0)
|
||||||
if (ret < 0)
|
return -1;
|
||||||
return ret;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If this removed entries from the index,
|
|
||||||
* what that means is:
|
|
||||||
*
|
|
||||||
* (1) the caller unpack_callback() saw path/foo
|
|
||||||
* in the index, and it has not removed it because
|
|
||||||
* it thinks it is handling 'path' as blob with
|
|
||||||
* D/F conflict;
|
|
||||||
* (2) we will return "ok, we placed a merged entry
|
|
||||||
* in the index" which would cause o->pos to be
|
|
||||||
* incremented by one;
|
|
||||||
* (3) however, original o->pos now has 'path/foo'
|
|
||||||
* marked with "to be removed".
|
|
||||||
*
|
|
||||||
* We need to increment it by the number of
|
|
||||||
* deleted entries here.
|
|
||||||
*/
|
|
||||||
o->pos += ret;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ struct unpack_trees_options {
|
||||||
diff_index_cached,
|
diff_index_cached,
|
||||||
gently;
|
gently;
|
||||||
const char *prefix;
|
const char *prefix;
|
||||||
int pos;
|
int cache_bottom;
|
||||||
struct dir_struct *dir;
|
struct dir_struct *dir;
|
||||||
merge_fn_t fn;
|
merge_fn_t fn;
|
||||||
struct unpack_trees_error_msgs msgs;
|
struct unpack_trees_error_msgs msgs;
|
||||||
|
|
Loading…
Reference in New Issue