summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPatrick Steinhardt <ps@pks.im>2018-09-21 15:18:03 +0200
committerPatrick Steinhardt <ps@pks.im>2018-10-19 12:28:28 +0200
commit8b6e28958c99b4f9effbbedb84ae779b3b1b9d98 (patch)
tree52c20bbbe0fcc7163b27561fa80b436c8c8250c6 /src
parent923317db3f5900230063a7715cec29aefdb8a713 (diff)
downloadlibgit2-8b6e28958c99b4f9effbbedb84ae779b3b1b9d98.tar.gz
index: fix adding index entries with conflicting files
When adding an index entry "a/b/c" while an index entry "a/b" already exists, git will happily remove "a/b/c" and only add the new index entry: $ git init test Initialized empty Git repository in /tmp/test.repo/test/.git/ $ touch x $ git add x $ rm x $ mkdir x $ touch x/y $ git add x/y $ git status A x/y The other way round, adding an index entry "a/b" with an entry "a/b/c" already existing is equivalent, where git will remove "a/b/c" and add "a/b". In contrast, libgit2 will currently fail to add these properly and instead complain about the entry appearing as both a file and a directory. This is a programming error, though: our current code already tries to detect and, in the case of `git_index_add`, to automatically replace such index entries. Funnily enough, we already remove the conflicting index entries, but instead of adding the new entry we then bail out afterwards. This leaves callers with the worst of both worlds: we both remove the old entry but fail to add the new one. The root cause is weird semantics of the `has_file_name` and `has_dir_name` functions. While these functions only sound like they are responsible for detecting such conflicts, they will also already remove them in case where its `ok_to_replace` parameter is set. But even if we tell it to replace such entries, it will return an error code. Fix the error by returning success in case where the entries have been replaced. Fix an already existing test which tested for wrong behaviour. Note that the test didn't notice that the resulting tree had no entries. Thus it is fine to change existing behaviour here, as the previous result could've let to silently loosing data. Also add a new test that verifies behaviour in the reverse conflicting case.
Diffstat (limited to 'src')
-rw-r--r--src/index.c22
1 files changed, 8 insertions, 14 deletions
diff --git a/src/index.c b/src/index.c
index 0e0dfe8fb..239a178f1 100644
--- a/src/index.c
+++ b/src/index.c
@@ -1096,7 +1096,6 @@ static int index_entry_dup_nocache(
static int has_file_name(git_index *index,
const git_index_entry *entry, size_t pos, int ok_to_replace)
{
- int retval = 0;
size_t len = strlen(entry->path);
int stage = GIT_IDXENTRY_STAGE(entry);
const char *name = entry->path;
@@ -1112,14 +1111,13 @@ static int has_file_name(git_index *index,
continue;
if (p->path[len] != '/')
continue;
- retval = -1;
if (!ok_to_replace)
- break;
+ return -1;
if (index_remove_entry(index, --pos) < 0)
break;
}
- return retval;
+ return 0;
}
/*
@@ -1129,7 +1127,6 @@ static int has_file_name(git_index *index,
static int has_dir_name(git_index *index,
const git_index_entry *entry, int ok_to_replace)
{
- int retval = 0;
int stage = GIT_IDXENTRY_STAGE(entry);
const char *name = entry->path;
const char *slash = name + strlen(name);
@@ -1141,14 +1138,13 @@ static int has_dir_name(git_index *index,
if (*--slash == '/')
break;
if (slash <= entry->path)
- return retval;
+ return 0;
}
len = slash - name;
if (!index_find(&pos, index, name, len, stage)) {
- retval = -1;
if (!ok_to_replace)
- break;
+ return -1;
if (index_remove_entry(index, pos) < 0)
break;
@@ -1169,20 +1165,18 @@ static int has_dir_name(git_index *index,
break; /* not our subdirectory */
if (GIT_IDXENTRY_STAGE(&p->entry) == stage)
- return retval;
+ return 0;
}
}
- return retval;
+ return 0;
}
static int check_file_directory_collision(git_index *index,
git_index_entry *entry, size_t pos, int ok_to_replace)
{
- int retval = has_file_name(index, entry, pos, ok_to_replace);
- retval = retval + has_dir_name(index, entry, ok_to_replace);
-
- if (retval) {
+ if (has_file_name(index, entry, pos, ok_to_replace) < 0 ||
+ has_dir_name(index, entry, ok_to_replace) < 0) {
giterr_set(GITERR_INDEX,
"'%s' appears as both a file and a directory", entry->path);
return -1;