Skip to content

Commit c819353

Browse files
author
Junio C Hamano
committed
Fix switching to a branch with D/F when current branch has file D.
This loosens the over-eager verify_absent() check that gets upset to find directory D in the current working tree when switching to a branch that has a file there. The check needs to make sure that we do not lose precious working tree files as a result of removing directory D and replacing it with the file from the other branch, which is a tad expensive but this is a less common case. Signed-off-by: Junio C Hamano <[email protected]>
1 parent b8ba153 commit c819353

File tree

1 file changed

+112
-1
lines changed

1 file changed

+112
-1
lines changed

unpack-trees.c

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,64 @@ static void invalidate_ce_path(struct cache_entry *ce)
465465
cache_tree_invalidate_path(active_cache_tree, ce->name);
466466
}
467467

468+
static int verify_clean_subdirectory(const char *path, const char *action,
469+
struct unpack_trees_options *o)
470+
{
471+
/*
472+
* we are about to extract "path"; we would not want to lose
473+
* anything in the existing directory there.
474+
*/
475+
int namelen;
476+
int pos, i;
477+
struct dir_struct d;
478+
char *pathbuf;
479+
int cnt = 0;
480+
481+
/*
482+
* First let's make sure we do not have a local modification
483+
* in that directory.
484+
*/
485+
namelen = strlen(path);
486+
pos = cache_name_pos(path, namelen);
487+
if (0 <= pos)
488+
return cnt; /* we have it as nondirectory */
489+
pos = -pos - 1;
490+
for (i = pos; i < active_nr; i++) {
491+
struct cache_entry *ce = active_cache[i];
492+
int len = ce_namelen(ce);
493+
if (len < namelen ||
494+
strncmp(path, ce->name, namelen) ||
495+
ce->name[namelen] != '/')
496+
break;
497+
/*
498+
* ce->name is an entry in the subdirectory.
499+
*/
500+
if (!ce_stage(ce)) {
501+
verify_uptodate(ce, o);
502+
ce->ce_mode = 0;
503+
}
504+
cnt++;
505+
}
506+
507+
/*
508+
* Then we need to make sure that we do not lose a locally
509+
* present file that is not ignored.
510+
*/
511+
pathbuf = xmalloc(namelen + 2);
512+
memcpy(pathbuf, path, namelen);
513+
strcpy(pathbuf+namelen, "/");
514+
515+
memset(&d, 0, sizeof(d));
516+
if (o->dir)
517+
d.exclude_per_dir = o->dir->exclude_per_dir;
518+
i = read_directory(&d, path, pathbuf, namelen+1, NULL);
519+
if (i)
520+
die("Updating '%s' would lose untracked files in it",
521+
path);
522+
free(pathbuf);
523+
return cnt;
524+
}
525+
468526
/*
469527
* We do not want to remove or overwrite a working tree file that
470528
* is not tracked, unless it is ignored.
@@ -476,9 +534,62 @@ static void verify_absent(const char *path, const char *action,
476534

477535
if (o->index_only || o->reset || !o->update)
478536
return;
479-
if (!lstat(path, &st) && !(o->dir && excluded(o->dir, path)))
537+
538+
if (!lstat(path, &st)) {
539+
int cnt;
540+
541+
if (o->dir && excluded(o->dir, path))
542+
/*
543+
* path is explicitly excluded, so it is Ok to
544+
* overwrite it.
545+
*/
546+
return;
547+
if (S_ISDIR(st.st_mode)) {
548+
/*
549+
* We are checking out path "foo" and
550+
* found "foo/." in the working tree.
551+
* This is tricky -- if we have modified
552+
* files that are in "foo/" we would lose
553+
* it.
554+
*/
555+
cnt = verify_clean_subdirectory(path, action, o);
556+
557+
/*
558+
* If this removed entries from the index,
559+
* what that means is:
560+
*
561+
* (1) the caller unpack_trees_rec() saw path/foo
562+
* in the index, and it has not removed it because
563+
* it thinks it is handling 'path' as blob with
564+
* D/F conflict;
565+
* (2) we will return "ok, we placed a merged entry
566+
* in the index" which would cause o->pos to be
567+
* incremented by one;
568+
* (3) however, original o->pos now has 'path/foo'
569+
* marked with "to be removed".
570+
*
571+
* We need to increment it by the number of
572+
* deleted entries here.
573+
*/
574+
o->pos += cnt;
575+
return;
576+
}
577+
578+
/*
579+
* The previous round may already have decided to
580+
* delete this path, which is in a subdirectory that
581+
* is being replaced with a blob.
582+
*/
583+
cnt = cache_name_pos(path, strlen(path));
584+
if (0 <= cnt) {
585+
struct cache_entry *ce = active_cache[cnt];
586+
if (!ce_stage(ce) && !ce->ce_mode)
587+
return;
588+
}
589+
480590
die("Untracked working tree file '%s' "
481591
"would be %s by merge.", path, action);
592+
}
482593
}
483594

484595
static int merged_entry(struct cache_entry *merge, struct cache_entry *old,

0 commit comments

Comments
 (0)