summaryrefslogtreecommitdiff
path: root/read-cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'read-cache.c')
-rw-r--r--read-cache.c113
1 files changed, 111 insertions, 2 deletions
diff --git a/read-cache.c b/read-cache.c
index a6fbf08..da2adf4 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -123,10 +123,112 @@ int same_name(struct cache_entry *a, struct cache_entry *b)
return ce_namelen(b) == len && !memcmp(a->name, b->name, len);
}
-int add_cache_entry(struct cache_entry *ce, int ok_to_add)
+/* We may be in a situation where we already have path/file and path
+ * is being added, or we already have path and path/file is being
+ * added. Either one would result in a nonsense tree that has path
+ * twice when git-write-tree tries to write it out. Prevent it.
+ *
+ * If ok-to-replace is specified, we remove the conflicting entries
+ * from the cache so the caller should recompute the insert position.
+ * When this happens, we return non-zero.
+ */
+static int check_file_directory_conflict(const struct cache_entry *ce,
+ int ok_to_replace)
{
- int pos;
+ int pos, replaced = 0;
+ const char *path = ce->name;
+ int namelen = strlen(path);
+ int stage = ce_stage(ce);
+ char *pathbuf = xmalloc(namelen + 1);
+ char *cp;
+
+ memcpy(pathbuf, path, namelen + 1);
+
+ /*
+ * We are inserting path/file. Do they have path registered at
+ * the same stage? We need to do this for all the levels of our
+ * subpath.
+ */
+ cp = pathbuf;
+ while (1) {
+ char *ep = strchr(cp, '/');
+ if (!ep)
+ break;
+ *ep = 0; /* first cut it at slash */
+ pos = cache_name_pos(pathbuf,
+ htons(create_ce_flags(ep-cp, stage)));
+ if (0 <= pos) {
+ /* Our leading path component is registered as a file,
+ * and we are trying to make it a directory. This is
+ * bad.
+ */
+ if (!ok_to_replace) {
+ free(pathbuf);
+ return -1;
+ }
+ fprintf(stderr, "removing file '%s' to replace it with a directory to create '%s'.\n", pathbuf, path);
+ remove_entry_at(pos);
+ replaced = 1;
+ }
+ *ep = '/'; /* then restore it and go downwards */
+ cp = ep + 1;
+ }
+ free(pathbuf);
+
+ /* Do we have an entry in the cache that makes our path a prefix
+ * of it? That is, are we creating a file where they already expect
+ * a directory there?
+ */
+ pos = cache_name_pos(path,
+ htons(create_ce_flags(namelen, stage)));
+
+ /* (0 <= pos) cannot happen because add_cache_entry()
+ * should have taken care of that case.
+ */
+ pos = -pos-1;
+ /* pos would point at an existing entry that would come immediately
+ * after our path. It could be the same as our path in higher stage,
+ * or different path but in a lower stage.
+ *
+ * E.g. when we are inserting path at stage 2,
+ *
+ * 1 path
+ * pos-> 3 path
+ * 2 path/file1
+ * 3 path/file1
+ * 2 path/file2
+ * 2 patho
+ *
+ * We need to examine pos, ignore it because it is at different
+ * stage, examine next to find the path/file at stage 2, and
+ * complain. We need to do this until we are not the leading
+ * path of an existing entry anymore.
+ */
+
+ while (pos < active_nr) {
+ struct cache_entry *other = active_cache[pos];
+ if (strncmp(other->name, path, namelen))
+ break; /* it is not our "subdirectory" anymore */
+ if ((ce_stage(other) == stage) &&
+ other->name[namelen] == '/') {
+ if (!ok_to_replace)
+ return -1;
+ fprintf(stderr, "removing file '%s' under '%s' to be replaced with a file\n", other->name, path);
+ remove_entry_at(pos);
+ replaced = 1;
+ continue; /* cycle without updating pos */
+ }
+ pos++;
+ }
+ return replaced;
+}
+
+int add_cache_entry(struct cache_entry *ce, int option)
+{
+ int pos;
+ int ok_to_add = option & ADD_CACHE_OK_TO_ADD;
+ int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE;
pos = cache_name_pos(ce->name, htons(ce->ce_flags));
/* existing match? Just replace it */
@@ -152,6 +254,13 @@ int add_cache_entry(struct cache_entry *ce, int ok_to_add)
if (!ok_to_add)
return -1;
+ if (check_file_directory_conflict(ce, ok_to_replace)) {
+ if (!ok_to_replace)
+ return -1;
+ pos = cache_name_pos(ce->name, htons(ce->ce_flags));
+ pos = -pos-1;
+ }
+
/* Make sure the array is big enough .. */
if (active_nr == active_alloc) {
active_alloc = alloc_nr(active_alloc);