Skip to content

Commit b878579

Browse files
pcloudsgitster
authored andcommitted
clone: report duplicate entries on case-insensitive filesystems
Paths that only differ in case work fine in a case-sensitive filesystems, but if those repos are cloned in a case-insensitive one, you'll get problems. The first thing to notice is "git status" will never be clean with no indication what exactly is "dirty". This patch helps the situation a bit by pointing out the problem at clone time. Even though this patch talks about case sensitivity, the patch makes no assumption about folding rules by the filesystem. It simply observes that if an entry has been already checked out at clone time when we're about to write a new path, some folding rules are behind this. In the case that we can't rely on filesystem (via inode number) to do this check, fall back to fspathcmp() which is not perfect but should not give false positives. This patch is tested with vim-colorschemes and Sublime-Gitignore repositories on a JFS partition with case insensitive support on Linux. Signed-off-by: Nguyễn Thái Ngọc Duy <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent ffc6fa0 commit b878579

File tree

6 files changed

+88
-1
lines changed

6 files changed

+88
-1
lines changed

builtin/clone.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,7 @@ static int checkout(int submodule_progress)
747747
memset(&opts, 0, sizeof opts);
748748
opts.update = 1;
749749
opts.merge = 1;
750+
opts.clone = 1;
750751
opts.fn = oneway_merge;
751752
opts.verbose_update = (option_verbosity >= 0);
752753
opts.src_index = &the_index;

cache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,7 @@ struct checkout {
14551455
unsigned force:1,
14561456
quiet:1,
14571457
not_new:1,
1458+
clone:1,
14581459
refresh_cache:1;
14591460
};
14601461
#define CHECKOUT_INIT { NULL, "" }

entry.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,34 @@ static int check_path(const char *path, int len, struct stat *st, int skiplen)
399399
return lstat(path, st);
400400
}
401401

402+
static void mark_colliding_entries(const struct checkout *state,
403+
struct cache_entry *ce, struct stat *st)
404+
{
405+
int i, trust_ino = check_stat;
406+
407+
#if defined(GIT_WINDOWS_NATIVE)
408+
trust_ino = 0;
409+
#endif
410+
411+
ce->ce_flags |= CE_MATCHED;
412+
413+
for (i = 0; i < state->istate->cache_nr; i++) {
414+
struct cache_entry *dup = state->istate->cache[i];
415+
416+
if (dup == ce)
417+
break;
418+
419+
if (dup->ce_flags & (CE_MATCHED | CE_VALID | CE_SKIP_WORKTREE))
420+
continue;
421+
422+
if ((trust_ino && dup->ce_stat_data.sd_ino == st->st_ino) ||
423+
(!trust_ino && !fspathcmp(ce->name, dup->name))) {
424+
dup->ce_flags |= CE_MATCHED;
425+
break;
426+
}
427+
}
428+
}
429+
402430
/*
403431
* Write the contents from ce out to the working tree.
404432
*
@@ -455,6 +483,9 @@ int checkout_entry(struct cache_entry *ce,
455483
return -1;
456484
}
457485

486+
if (state->clone)
487+
mark_colliding_entries(state, ce, &st);
488+
458489
/*
459490
* We unlink the old file, to get the new one with the
460491
* right permissions (including umask, which is nasty

t/t5601-clone.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,10 +624,16 @@ test_expect_success 'clone on case-insensitive fs' '
624624
git hash-object -w -t tree --stdin) &&
625625
c=$(git commit-tree -m bogus $t) &&
626626
git update-ref refs/heads/bogus $c &&
627-
git clone -b bogus . bogus
627+
git clone -b bogus . bogus 2>warning
628628
)
629629
'
630630

631+
test_expect_success !MINGW,!CYGWIN,CASE_INSENSITIVE_FS 'colliding file detection' '
632+
grep X icasefs/warning &&
633+
grep x icasefs/warning &&
634+
test_i18ngrep "the following paths have collided" icasefs/warning
635+
'
636+
631637
partial_clone () {
632638
SERVER="$1" &&
633639
URL="$2" &&

unpack-trees.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,46 @@ static struct progress *get_progress(struct unpack_trees_options *o)
345345
return start_delayed_progress(_("Checking out files"), total);
346346
}
347347

348+
static void setup_collided_checkout_detection(struct checkout *state,
349+
struct index_state *index)
350+
{
351+
int i;
352+
353+
state->clone = 1;
354+
for (i = 0; i < index->cache_nr; i++)
355+
index->cache[i]->ce_flags &= ~CE_MATCHED;
356+
}
357+
358+
static void report_collided_checkout(struct index_state *index)
359+
{
360+
struct string_list list = STRING_LIST_INIT_NODUP;
361+
int i;
362+
363+
for (i = 0; i < index->cache_nr; i++) {
364+
struct cache_entry *ce = index->cache[i];
365+
366+
if (!(ce->ce_flags & CE_MATCHED))
367+
continue;
368+
369+
string_list_append(&list, ce->name);
370+
ce->ce_flags &= ~CE_MATCHED;
371+
}
372+
373+
list.cmp = fspathcmp;
374+
string_list_sort(&list);
375+
376+
if (list.nr) {
377+
warning(_("the following paths have collided (e.g. case-sensitive paths\n"
378+
"on a case-insensitive filesystem) and only one from the same\n"
379+
"colliding group is in the working tree:\n"));
380+
381+
for (i = 0; i < list.nr; i++)
382+
fprintf(stderr, " '%s'\n", list.items[i].string);
383+
}
384+
385+
string_list_clear(&list, 0);
386+
}
387+
348388
static int check_updates(struct unpack_trees_options *o)
349389
{
350390
unsigned cnt = 0;
@@ -359,6 +399,9 @@ static int check_updates(struct unpack_trees_options *o)
359399
state.refresh_cache = 1;
360400
state.istate = index;
361401

402+
if (o->clone)
403+
setup_collided_checkout_detection(&state, index);
404+
362405
progress = get_progress(o);
363406

364407
if (o->update)
@@ -423,6 +466,10 @@ static int check_updates(struct unpack_trees_options *o)
423466
errs |= finish_delayed_checkout(&state);
424467
if (o->update)
425468
git_attr_set_direction(GIT_ATTR_CHECKIN, NULL);
469+
470+
if (o->clone)
471+
report_collided_checkout(index);
472+
426473
return errs != 0;
427474
}
428475

unpack-trees.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ struct unpack_trees_options {
4242
unsigned int reset,
4343
merge,
4444
update,
45+
clone,
4546
index_only,
4647
nontrivial_merge,
4748
trivial_merges_only,

0 commit comments

Comments
 (0)