Fix switching to a branch with D/F when current branch has file D.
authorJunio C Hamano <junkio@cox.net>
Fri, 16 Mar 2007 06:25:22 +0000 (23:25 -0700)
committerJunio C Hamano <junkio@cox.net>
Wed, 4 Apr 2007 07:25:10 +0000 (00:25 -0700)
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 <junkio@cox.net>
unpack-trees.c

index d0367846d4d1d86a16c4c9d20dadb68fa35a94d9..a0b676903ad042bfc1bb199a33f1658cdfd511c9 100644 (file)
@@ -465,6 +465,64 @@ static void invalidate_ce_path(struct cache_entry *ce)
                cache_tree_invalidate_path(active_cache_tree, ce->name);
 }
 
+static int verify_clean_subdirectory(const char *path, const char *action,
+                                     struct unpack_trees_options *o)
+{
+       /*
+        * we are about to extract "path"; we would not want to lose
+        * anything in the existing directory there.
+        */
+       int namelen;
+       int pos, i;
+       struct dir_struct d;
+       char *pathbuf;
+       int cnt = 0;
+
+       /*
+        * First let's make sure we do not have a local modification
+        * in that directory.
+        */
+       namelen = strlen(path);
+       pos = cache_name_pos(path, namelen);
+       if (0 <= pos)
+               return cnt; /* we have it as nondirectory */
+       pos = -pos - 1;
+       for (i = pos; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               int len = ce_namelen(ce);
+               if (len < namelen ||
+                   strncmp(path, ce->name, namelen) ||
+                   ce->name[namelen] != '/')
+                       break;
+               /*
+                * ce->name is an entry in the subdirectory.
+                */
+               if (!ce_stage(ce)) {
+                       verify_uptodate(ce, o);
+                       ce->ce_mode = 0;
+               }
+               cnt++;
+       }
+
+       /*
+        * Then we need to make sure that we do not lose a locally
+        * present file that is not ignored.
+        */
+       pathbuf = xmalloc(namelen + 2);
+       memcpy(pathbuf, path, namelen);
+       strcpy(pathbuf+namelen, "/");
+
+       memset(&d, 0, sizeof(d));
+       if (o->dir)
+               d.exclude_per_dir = o->dir->exclude_per_dir;
+       i = read_directory(&d, path, pathbuf, namelen+1, NULL);
+       if (i)
+               die("Updating '%s' would lose untracked files in it",
+                   path);
+       free(pathbuf);
+       return cnt;
+}
+
 /*
  * We do not want to remove or overwrite a working tree file that
  * is not tracked, unless it is ignored.
@@ -476,9 +534,62 @@ static void verify_absent(const char *path, const char *action,
 
        if (o->index_only || o->reset || !o->update)
                return;
-       if (!lstat(path, &st) && !(o->dir && excluded(o->dir, path)))
+
+       if (!lstat(path, &st)) {
+               int cnt;
+
+               if (o->dir && excluded(o->dir, path))
+                       /*
+                        * path is explicitly excluded, so it is Ok to
+                        * overwrite it.
+                        */
+                       return;
+               if (S_ISDIR(st.st_mode)) {
+                       /*
+                        * We are checking out path "foo" and
+                        * found "foo/." in the working tree.
+                        * This is tricky -- if we have modified
+                        * files that are in "foo/" we would lose
+                        * it.
+                        */
+                       cnt = verify_clean_subdirectory(path, action, o);
+
+                       /*
+                        * If this removed entries from the index,
+                        * what that means is:
+                        *
+                        * (1) the caller unpack_trees_rec() saw path/foo
+                        * in the index, and it has not removed it because
+                        * it thinks it is handling 'path' as blob with
+                        * D/F conflict;
+                        * (2) we will return "ok, we placed a merged entry
+                        * in the index" which would cause o->pos to be
+                        * incremented by one;
+                        * (3) however, original o->pos now has 'path/foo'
+                        * marked with "to be removed".
+                        *
+                        * We need to increment it by the number of
+                        * deleted entries here.
+                        */
+                       o->pos += cnt;
+                       return;
+               }
+
+               /*
+                * The previous round may already have decided to
+                * delete this path, which is in a subdirectory that
+                * is being replaced with a blob.
+                */
+               cnt = cache_name_pos(path, strlen(path));
+               if (0 <= cnt) {
+                       struct cache_entry *ce = active_cache[cnt];
+                       if (!ce_stage(ce) && !ce->ce_mode)
+                               return;
+               }
+
                die("Untracked working tree file '%s' "
                    "would be %s by merge.", path, action);
+       }
 }
 
 static int merged_entry(struct cache_entry *merge, struct cache_entry *old,