summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--builtin-read-tree.c1
-rw-r--r--read-cache.c40
-rw-r--r--unpack-trees.c153
-rw-r--r--unpack-trees.h1
4 files changed, 167 insertions, 28 deletions
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 213bd93c7f..316fb0f8da 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -233,6 +233,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
if (0 <= pos)
die("file '%.*s' already exists.",
pfxlen-1, opts.prefix);
+ opts.pos = -1 - pos;
}
if (opts.merge) {
diff --git a/read-cache.c b/read-cache.c
index a8f8a6b2b2..54573ce2ee 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -485,6 +485,8 @@ static int has_file_name(const struct cache_entry *ce, int pos, int ok_to_replac
continue;
if (p->name[len] != '/')
continue;
+ if (!ce_stage(p) && !p->ce_mode)
+ continue;
retval = -1;
if (!ok_to_replace)
break;
@@ -517,26 +519,37 @@ static int has_dir_name(const struct cache_entry *ce, int pos, int ok_to_replace
pos = cache_name_pos(name, ntohs(create_ce_flags(len, stage)));
if (pos >= 0) {
- retval = -1;
- if (!ok_to_replace)
- break;
- remove_cache_entry_at(pos);
- continue;
+ /*
+ * Found one, but not so fast. This could
+ * be a marker that says "I was here, but
+ * I am being removed". Such an entry is
+ * not a part of the resulting tree, and
+ * it is Ok to have a directory at the same
+ * path.
+ */
+ if (stage || active_cache[pos]->ce_mode) {
+ retval = -1;
+ if (!ok_to_replace)
+ break;
+ remove_cache_entry_at(pos);
+ continue;
+ }
}
+ else
+ pos = -pos-1;
/*
* Trivial optimization: if we find an entry that
* already matches the sub-directory, then we know
* we're ok, and we can exit.
*/
- pos = -pos-1;
while (pos < active_nr) {
struct cache_entry *p = active_cache[pos];
if ((ce_namelen(p) <= len) ||
(p->name[len] != '/') ||
memcmp(p->name, name, len))
break; /* not our subdirectory */
- if (ce_stage(p) == stage)
+ if (ce_stage(p) == stage && (stage || p->ce_mode))
/* p is at the same stage as our entry, and
* is a subdirectory of what we are looking
* at, so we cannot have conflicts at our
@@ -560,12 +573,21 @@ static int has_dir_name(const struct cache_entry *ce, int pos, int ok_to_replace
*/
static int check_file_directory_conflict(const struct cache_entry *ce, int pos, int ok_to_replace)
{
+ int retval;
+
+ /*
+ * When ce is an "I am going away" entry, we allow it to be added
+ */
+ if (!ce_stage(ce) && !ce->ce_mode)
+ return 0;
+
/*
* We check if the path is a sub-path of a subsequent pathname
* first, since removing those will not change the position
- * in the array
+ * in the array.
*/
- int retval = has_file_name(ce, pos, ok_to_replace);
+ retval = has_file_name(ce, pos, ok_to_replace);
+
/*
* Then check if the path might have a clashing sub-directory
* before it.
diff --git a/unpack-trees.c b/unpack-trees.c
index ee10eea24c..a0b676903a 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -70,7 +70,6 @@ static int entcmp(const char *name1, int dir1, const char *name2, int dir2)
static int unpack_trees_rec(struct tree_entry_list **posns, int len,
const char *base, struct unpack_trees_options *o,
- int *indpos,
struct tree_entry_list *df_conflict_list)
{
int baselen = strlen(base);
@@ -100,7 +99,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
cache_name = NULL;
/* Check the cache */
- if (o->merge && *indpos < active_nr) {
+ if (o->merge && o->pos < active_nr) {
/* This is a bit tricky: */
/* If the index has a subdirectory (with
* contents) as the first name, it'll get a
@@ -118,7 +117,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
* file case.
*/
- cache_name = active_cache[*indpos]->name;
+ cache_name = active_cache[o->pos]->name;
if (strlen(cache_name) > baselen &&
!memcmp(cache_name, base, baselen)) {
cache_name += baselen;
@@ -158,8 +157,8 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
if (cache_name && !strcmp(cache_name, first)) {
any_files = 1;
- src[0] = active_cache[*indpos];
- remove_cache_entry_at(*indpos);
+ src[0] = active_cache[o->pos];
+ remove_cache_entry_at(o->pos);
}
for (i = 0; i < len; i++) {
@@ -228,7 +227,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
#if DBRT_DEBUG > 1
printf("Added %d entries\n", ret);
#endif
- *indpos += ret;
+ o->pos += ret;
} else {
for (i = 0; i < src_size; i++) {
if (src[i]) {
@@ -244,7 +243,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
newbase[baselen + pathlen] = '/';
newbase[baselen + pathlen + 1] = '\0';
if (unpack_trees_rec(subposns, len, newbase, o,
- indpos, df_conflict_list)) {
+ df_conflict_list)) {
retval = -1;
goto leave_directory;
}
@@ -375,7 +374,6 @@ static void check_updates(struct cache_entry **src, int nr,
int unpack_trees(struct object_list *trees, struct unpack_trees_options *o)
{
- int indpos = 0;
unsigned len = object_list_length(trees);
struct tree_entry_list **posns;
int i;
@@ -404,7 +402,7 @@ int unpack_trees(struct object_list *trees, struct unpack_trees_options *o)
posn = posn->next;
}
if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "",
- o, &indpos, &df_conflict_list))
+ o, &df_conflict_list))
return -1;
}
@@ -467,6 +465,64 @@ static void invalidate_ce_path(struct cache_entry *ce)
cache_tree_invalidate_path(active_cache_tree, ce->name);
}
+static int verify_clean_subdirectory(const char *path, const char *action,
+ struct unpack_trees_options *o)
+{
+ /*
+ * we are about to extract "path"; we would not want to lose
+ * anything in the existing directory there.
+ */
+ int namelen;
+ int pos, i;
+ struct dir_struct d;
+ char *pathbuf;
+ int cnt = 0;
+
+ /*
+ * First let's make sure we do not have a local modification
+ * in that directory.
+ */
+ namelen = strlen(path);
+ pos = cache_name_pos(path, namelen);
+ if (0 <= pos)
+ return cnt; /* we have it as nondirectory */
+ pos = -pos - 1;
+ for (i = pos; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ int len = ce_namelen(ce);
+ if (len < namelen ||
+ strncmp(path, ce->name, namelen) ||
+ ce->name[namelen] != '/')
+ break;
+ /*
+ * ce->name is an entry in the subdirectory.
+ */
+ if (!ce_stage(ce)) {
+ verify_uptodate(ce, o);
+ ce->ce_mode = 0;
+ }
+ cnt++;
+ }
+
+ /*
+ * Then we need to make sure that we do not lose a locally
+ * present file that is not ignored.
+ */
+ pathbuf = xmalloc(namelen + 2);
+ memcpy(pathbuf, path, namelen);
+ strcpy(pathbuf+namelen, "/");
+
+ memset(&d, 0, sizeof(d));
+ if (o->dir)
+ d.exclude_per_dir = o->dir->exclude_per_dir;
+ i = read_directory(&d, path, pathbuf, namelen+1, NULL);
+ if (i)
+ die("Updating '%s' would lose untracked files in it",
+ path);
+ free(pathbuf);
+ return cnt;
+}
+
/*
* We do not want to remove or overwrite a working tree file that
* is not tracked, unless it is ignored.
@@ -478,9 +534,62 @@ static void verify_absent(const char *path, const char *action,
if (o->index_only || o->reset || !o->update)
return;
- if (!lstat(path, &st) && !(o->dir && excluded(o->dir, path)))
+
+ if (!lstat(path, &st)) {
+ int cnt;
+
+ if (o->dir && excluded(o->dir, path))
+ /*
+ * path is explicitly excluded, so it is Ok to
+ * overwrite it.
+ */
+ return;
+ if (S_ISDIR(st.st_mode)) {
+ /*
+ * We are checking out path "foo" and
+ * found "foo/." in the working tree.
+ * This is tricky -- if we have modified
+ * files that are in "foo/" we would lose
+ * it.
+ */
+ cnt = verify_clean_subdirectory(path, action, o);
+
+ /*
+ * If this removed entries from the index,
+ * what that means is:
+ *
+ * (1) the caller unpack_trees_rec() 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 += cnt;
+ return;
+ }
+
+ /*
+ * The previous round may already have decided to
+ * delete this path, which is in a subdirectory that
+ * is being replaced with a blob.
+ */
+ cnt = cache_name_pos(path, strlen(path));
+ if (0 <= cnt) {
+ struct cache_entry *ce = active_cache[cnt];
+ if (!ce_stage(ce) && !ce->ce_mode)
+ return;
+ }
+
die("Untracked working tree file '%s' "
"would be %s by merge.", path, action);
+ }
}
static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
@@ -525,7 +634,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
return 1;
}
-static int keep_entry(struct cache_entry *ce)
+static int keep_entry(struct cache_entry *ce, struct unpack_trees_options *o)
{
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
return 1;
@@ -682,7 +791,7 @@ int threeway_merge(struct cache_entry **stages,
if (!head_match || !remote_match) {
for (i = 1; i < o->head_idx; i++) {
if (stages[i]) {
- keep_entry(stages[i]);
+ keep_entry(stages[i], o);
count++;
break;
}
@@ -695,8 +804,8 @@ int threeway_merge(struct cache_entry **stages,
show_stage_entry(stderr, "remote ", stages[remote_match]);
}
#endif
- if (head) { count += keep_entry(head); }
- if (remote) { count += keep_entry(remote); }
+ if (head) { count += keep_entry(head, o); }
+ if (remote) { count += keep_entry(remote, o); }
return count;
}
@@ -713,12 +822,18 @@ int twoway_merge(struct cache_entry **src,
struct unpack_trees_options *o)
{
struct cache_entry *current = src[0];
- struct cache_entry *oldtree = src[1], *newtree = src[2];
+ struct cache_entry *oldtree = src[1];
+ struct cache_entry *newtree = src[2];
if (o->merge_size != 2)
return error("Cannot do a twoway merge of %d trees",
o->merge_size);
+ if (oldtree == o->df_conflict_entry)
+ oldtree = NULL;
+ if (newtree == o->df_conflict_entry)
+ newtree = NULL;
+
if (current) {
if ((!oldtree && !newtree) || /* 4 and 5 */
(!oldtree && newtree &&
@@ -726,9 +841,9 @@ int twoway_merge(struct cache_entry **src,
(oldtree && newtree &&
same(oldtree, newtree)) || /* 14 and 15 */
(oldtree && newtree &&
- !same(oldtree, newtree) && /* 18 and 19*/
+ !same(oldtree, newtree) && /* 18 and 19 */
same(current, newtree))) {
- return keep_entry(current);
+ return keep_entry(current, o);
}
else if (oldtree && !newtree && same(current, oldtree)) {
/* 10 or 11 */
@@ -774,7 +889,7 @@ int bind_merge(struct cache_entry **src,
if (a && old)
die("Entry '%s' overlaps. Cannot bind.", a->name);
if (!a)
- return keep_entry(old);
+ return keep_entry(old, o);
else
return merged_entry(a, NULL, o);
}
@@ -804,7 +919,7 @@ int oneway_merge(struct cache_entry **src,
ce_match_stat(old, &st, 1))
old->ce_flags |= htons(CE_UPDATE);
}
- return keep_entry(old);
+ return keep_entry(old, o);
}
return merged_entry(a, old, o);
}
diff --git a/unpack-trees.h b/unpack-trees.h
index 191f7442f1..fee7da4382 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -16,6 +16,7 @@ struct unpack_trees_options {
int verbose_update;
int aggressive;
const char *prefix;
+ int pos;
struct dir_struct *dir;
merge_fn_t fn;