Skip to content

Commit 28239be

Browse files
authored
Merge pull request libgit2#4818 from pks-t/pks/index-collision
Index collision fixes
2 parents 11fbead + 8b6e289 commit 28239be

File tree

2 files changed

+89
-47
lines changed

2 files changed

+89
-47
lines changed

src/index.c

Lines changed: 40 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,7 +1096,6 @@ static int index_entry_dup_nocache(
10961096
static int has_file_name(git_index *index,
10971097
const git_index_entry *entry, size_t pos, int ok_to_replace)
10981098
{
1099-
int retval = 0;
11001099
size_t len = strlen(entry->path);
11011100
int stage = GIT_IDXENTRY_STAGE(entry);
11021101
const char *name = entry->path;
@@ -1112,14 +1111,13 @@ static int has_file_name(git_index *index,
11121111
continue;
11131112
if (p->path[len] != '/')
11141113
continue;
1115-
retval = -1;
11161114
if (!ok_to_replace)
1117-
break;
1115+
return -1;
11181116

11191117
if (index_remove_entry(index, --pos) < 0)
11201118
break;
11211119
}
1122-
return retval;
1120+
return 0;
11231121
}
11241122

11251123
/*
@@ -1129,7 +1127,6 @@ static int has_file_name(git_index *index,
11291127
static int has_dir_name(git_index *index,
11301128
const git_index_entry *entry, int ok_to_replace)
11311129
{
1132-
int retval = 0;
11331130
int stage = GIT_IDXENTRY_STAGE(entry);
11341131
const char *name = entry->path;
11351132
const char *slash = name + strlen(name);
@@ -1141,14 +1138,13 @@ static int has_dir_name(git_index *index,
11411138
if (*--slash == '/')
11421139
break;
11431140
if (slash <= entry->path)
1144-
return retval;
1141+
return 0;
11451142
}
11461143
len = slash - name;
11471144

11481145
if (!index_find(&pos, index, name, len, stage)) {
1149-
retval = -1;
11501146
if (!ok_to_replace)
1151-
break;
1147+
return -1;
11521148

11531149
if (index_remove_entry(index, pos) < 0)
11541150
break;
@@ -1169,20 +1165,18 @@ static int has_dir_name(git_index *index,
11691165
break; /* not our subdirectory */
11701166

11711167
if (GIT_IDXENTRY_STAGE(&p->entry) == stage)
1172-
return retval;
1168+
return 0;
11731169
}
11741170
}
11751171

1176-
return retval;
1172+
return 0;
11771173
}
11781174

11791175
static int check_file_directory_collision(git_index *index,
11801176
git_index_entry *entry, size_t pos, int ok_to_replace)
11811177
{
1182-
int retval = has_file_name(index, entry, pos, ok_to_replace);
1183-
retval = retval + has_dir_name(index, entry, ok_to_replace);
1184-
1185-
if (retval) {
1178+
if (has_file_name(index, entry, pos, ok_to_replace) < 0 ||
1179+
has_dir_name(index, entry, ok_to_replace) < 0) {
11861180
giterr_set(GITERR_INDEX,
11871181
"'%s' appears as both a file and a directory", entry->path);
11881182
return -1;
@@ -1337,57 +1331,58 @@ static int index_insert(
13371331
bool trust_mode,
13381332
bool trust_id)
13391333
{
1340-
int error = 0;
1341-
size_t path_length, position;
13421334
git_index_entry *existing, *best, *entry;
1335+
size_t path_length, position;
1336+
int error;
13431337

13441338
assert(index && entry_ptr);
13451339

13461340
entry = *entry_ptr;
13471341

1348-
/* make sure that the path length flag is correct */
1342+
/* Make sure that the path length flag is correct */
13491343
path_length = ((struct entry_internal *)entry)->pathlen;
13501344
index_entry_adjust_namemask(entry, path_length);
13511345

1352-
/* this entry is now up-to-date and should not be checked for raciness */
1346+
/* This entry is now up-to-date and should not be checked for raciness */
13531347
entry->flags_extended |= GIT_IDXENTRY_UPTODATE;
13541348

13551349
git_vector_sort(&index->entries);
13561350

1357-
/* look if an entry with this path already exists, either staged, or (if
1351+
/*
1352+
* Look if an entry with this path already exists, either staged, or (if
13581353
* this entry is a regular staged item) as the "ours" side of a conflict.
13591354
*/
13601355
index_existing_and_best(&existing, &position, &best, index, entry);
13611356

1362-
/* update the file mode */
1357+
/* Update the file mode */
13631358
entry->mode = trust_mode ?
13641359
git_index__create_mode(entry->mode) :
13651360
index_merge_mode(index, best, entry->mode);
13661361

1367-
/* canonicalize the directory name */
1368-
if (!trust_path)
1369-
error = canonicalize_directory_path(index, entry, best);
1362+
/* Canonicalize the directory name */
1363+
if (!trust_path && (error = canonicalize_directory_path(index, entry, best)) < 0)
1364+
goto out;
13701365

1371-
/* ensure that the given id exists (unless it's a submodule) */
1372-
if (!error && !trust_id && INDEX_OWNER(index) &&
1373-
(entry->mode & GIT_FILEMODE_COMMIT) != GIT_FILEMODE_COMMIT) {
1366+
/* Ensure that the given id exists (unless it's a submodule) */
1367+
if (!trust_id && INDEX_OWNER(index) &&
1368+
(entry->mode & GIT_FILEMODE_COMMIT) != GIT_FILEMODE_COMMIT) {
13741369

13751370
if (!git_object__is_valid(INDEX_OWNER(index), &entry->id,
1376-
git_object__type_from_filemode(entry->mode)))
1371+
git_object__type_from_filemode(entry->mode))) {
13771372
error = -1;
1373+
goto out;
1374+
}
13781375
}
13791376

1380-
/* look for tree / blob name collisions, removing conflicts if requested */
1381-
if (!error)
1382-
error = check_file_directory_collision(index, entry, position, replace);
1383-
1384-
if (error < 0)
1385-
/* skip changes */;
1377+
/* Look for tree / blob name collisions, removing conflicts if requested */
1378+
if ((error = check_file_directory_collision(index, entry, position, replace)) < 0)
1379+
goto out;
13861380

1387-
/* if we are replacing an existing item, overwrite the existing entry
1381+
/*
1382+
* If we are replacing an existing item, overwrite the existing entry
13881383
* and return it in place of the passed in one.
13891384
*/
1390-
else if (existing) {
1385+
if (existing) {
13911386
if (replace) {
13921387
index_entry_cpy(existing, entry);
13931388

@@ -1396,25 +1391,25 @@ static int index_insert(
13961391
}
13971392

13981393
index_entry_free(entry);
1399-
*entry_ptr = entry = existing;
1400-
}
1401-
else {
1402-
/* if replace is not requested or no existing entry exists, insert
1394+
*entry_ptr = existing;
1395+
} else {
1396+
/*
1397+
* If replace is not requested or no existing entry exists, insert
14031398
* at the sorted position. (Since we re-sort after each insert to
14041399
* check for dups, this is actually cheaper in the long run.)
14051400
*/
1406-
error = git_vector_insert_sorted(&index->entries, entry, index_no_dups);
1401+
if ((error = git_vector_insert_sorted(&index->entries, entry, index_no_dups)) < 0)
1402+
goto out;
14071403

1408-
if (error == 0) {
1409-
INSERT_IN_MAP(index, entry, &error);
1410-
}
1404+
INSERT_IN_MAP(index, entry, &error);
14111405
}
14121406

1407+
index->dirty = 1;
1408+
1409+
out:
14131410
if (error < 0) {
14141411
index_entry_free(*entry_ptr);
14151412
*entry_ptr = NULL;
1416-
} else {
1417-
index->dirty = 1;
14181413
}
14191414

14201415
return error;

tests/index/collision.c

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ void test_index_collision__cleanup(void)
2323
cl_git_sandbox_cleanup();
2424
}
2525

26-
void test_index_collision__add(void)
26+
void test_index_collision__add_blob_with_conflicting_file(void)
2727
{
2828
git_index_entry entry;
29+
git_tree_entry *tentry;
2930
git_oid tree_id;
3031
git_tree *tree;
3132

@@ -39,13 +40,59 @@ void test_index_collision__add(void)
3940
entry.path = "a/b";
4041
cl_git_pass(git_index_add(g_index, &entry));
4142

43+
/* Check a/b exists here */
44+
cl_git_pass(git_index_write_tree(&tree_id, g_index));
45+
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
46+
cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b"));
47+
git_tree_entry_free(tentry);
48+
git_tree_free(tree);
49+
4250
/* create a tree/blob collision */
4351
entry.path = "a/b/c";
44-
cl_git_fail(git_index_add(g_index, &entry));
52+
cl_git_pass(git_index_add(g_index, &entry));
4553

54+
/* a/b should now be a tree and a/b/c a blob */
4655
cl_git_pass(git_index_write_tree(&tree_id, g_index));
4756
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
57+
cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b/c"));
58+
git_tree_entry_free(tentry);
59+
git_tree_free(tree);
60+
}
61+
62+
void test_index_collision__add_blob_with_conflicting_dir(void)
63+
{
64+
git_index_entry entry;
65+
git_tree_entry *tentry;
66+
git_oid tree_id;
67+
git_tree *tree;
4868

69+
memset(&entry, 0, sizeof(entry));
70+
entry.ctime.seconds = 12346789;
71+
entry.mtime.seconds = 12346789;
72+
entry.mode = 0100644;
73+
entry.file_size = 0;
74+
git_oid_cpy(&entry.id, &g_empty_id);
75+
76+
entry.path = "a/b/c";
77+
cl_git_pass(git_index_add(g_index, &entry));
78+
79+
/* Check a/b/c exists here */
80+
cl_git_pass(git_index_write_tree(&tree_id, g_index));
81+
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
82+
cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b/c"));
83+
git_tree_entry_free(tentry);
84+
git_tree_free(tree);
85+
86+
/* create a blob/tree collision */
87+
entry.path = "a/b";
88+
cl_git_pass(git_index_add(g_index, &entry));
89+
90+
/* a/b should now be a tree and a/b/c a blob */
91+
cl_git_pass(git_index_write_tree(&tree_id, g_index));
92+
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
93+
cl_git_pass(git_tree_entry_bypath(&tentry, tree, "a/b"));
94+
cl_git_fail(git_tree_entry_bypath(&tentry, tree, "a/b/c"));
95+
git_tree_entry_free(tentry);
4996
git_tree_free(tree);
5097
}
5198

0 commit comments

Comments
 (0)