read-tree: further loosen "working file will be lost" check.
authorJunio C Hamano <junkio@cox.net>
Tue, 5 Dec 2006 00:00:46 +0000 (16:00 -0800)
committerJunio C Hamano <junkio@cox.net>
Wed, 6 Dec 2006 07:25:52 +0000 (23:25 -0800)
This follows up commit ed93b449 where we removed overcautious
"working file will be lost" check.

A new option "--exclude-per-directory=.gitignore" can be used to
tell the "git-read-tree" command that the user does not mind
losing contents in untracked files in the working tree, if they
need to be overwritten by a merge (either a two-way "switch
branches" merge, or a three-way merge).

Signed-off-by: Junio C Hamano <junkio@cox.net>
builtin-read-tree.c
dir.c
dir.h
t/t1004-read-tree-m-u-wf.sh
unpack-trees.c
unpack-trees.h

index c1867d2a0052ad797481f3d5f9aa544952ff251b..3f6cae36ce9b730bf6b76f3f1fedd006f356854b 100644 (file)
@@ -10,6 +10,7 @@
 #include "tree-walk.h"
 #include "cache-tree.h"
 #include "unpack-trees.h"
+#include "dir.h"
 #include "builtin.h"
 
 static struct object_list *trees;
@@ -178,6 +179,23 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                        continue;
                }
 
+               if (!strncmp(arg, "--exclude-per-directory=", 24)) {
+                       struct dir_struct *dir;
+
+                       if (opts.dir)
+                               die("more than one --exclude-per-directory are given.");
+
+                       dir = calloc(1, sizeof(*opts.dir));
+                       dir->show_ignored = 1;
+                       dir->exclude_per_dir = arg + 24;
+                       opts.dir = dir;
+                       /* We do not need to nor want to do read-directory
+                        * here; we are merely interested in reusing the
+                        * per directory ignore stack mechanism.
+                        */
+                       continue;
+               }
+
                /* using -u and -i at the same time makes no sense */
                if (1 < opts.index_only + opts.update)
                        usage(read_tree_usage);
@@ -190,6 +208,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
        }
        if ((opts.update||opts.index_only) && !opts.merge)
                usage(read_tree_usage);
+       if ((opts.dir && !opts.update))
+               die("--exclude-per-directory is meaningless unless -u");
 
        if (opts.prefix) {
                int pfxlen = strlen(opts.prefix);
diff --git a/dir.c b/dir.c
index 96389b32e66af24896220e4738cd93bf2ac75704..e6a61ee913d9f0f4f56402bdb451689cb80373ab 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -156,7 +156,7 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)
                die("cannot use %s as an exclude file", fname);
 }
 
-static int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
+int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
 {
        char exclude_file[PATH_MAX];
        struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
@@ -170,7 +170,7 @@ static int push_exclude_per_directory(struct dir_struct *dir, const char *base,
        return current_nr;
 }
 
-static void pop_exclude_per_directory(struct dir_struct *dir, int stk)
+void pop_exclude_per_directory(struct dir_struct *dir, int stk)
 {
        struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
 
diff --git a/dir.h b/dir.h
index 313f8ab64e7e3a6be1d22335eb54872c929d857e..550551ab25372c1898ba790e973808ccf0450924 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -43,6 +43,9 @@ extern int common_prefix(const char **pathspec);
 extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
 
 extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen);
+extern int push_exclude_per_directory(struct dir_struct *, const char *, int);
+extern void pop_exclude_per_directory(struct dir_struct *, int);
+
 extern int excluded(struct dir_struct *, const char *);
 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
 extern void add_exclude(const char *string, const char *base,
index 018fbea450b3481bea2586769de7418dea929e29..4f664f6adfb6153058848d3a3016c34e08b9425c 100755 (executable)
@@ -8,23 +8,27 @@ test_description='read-tree -m -u checks working tree files'
 
 test_expect_success 'two-way setup' '
 
+       mkdir subdir &&
        echo >file1 file one &&
        echo >file2 file two &&
-       git update-index --add file1 file2 &&
+       echo >subdir/file1 file one in subdirectory &&
+       echo >subdir/file2 file two in subdirectory &&
+       git update-index --add file1 file2 subdir/file1 subdir/file2 &&
        git commit -m initial &&
 
        git branch side &&
        git tag -f branch-point &&
 
        echo file2 is not tracked on the master anymore &&
-       rm -f file2 &&
-       git update-index --remove file2 &&
-       git commit -a -m "master removes file2"
+       rm -f file2 subdir/file2 &&
+       git update-index --remove file2 subdir/file2 &&
+       git commit -a -m "master removes file2 and subdir/file2"
 '
 
 test_expect_success 'two-way not clobbering' '
 
        echo >file2 master creates untracked file2 &&
+       echo >subdir/file2 master creates untracked subdir/file2 &&
        if err=`git read-tree -m -u master side 2>&1`
        then
                echo should have complained
@@ -34,20 +38,82 @@ test_expect_success 'two-way not clobbering' '
        fi
 '
 
+echo file2 >.gitignore
+
+test_expect_success 'two-way with incorrect --exclude-per-directory (1)' '
+
+       if err=`git read-tree -m --exclude-per-directory=.gitignore master side 2>&1`
+       then
+               echo should have complained
+               false
+       else
+               echo "happy to see $err"
+       fi
+'
+
+test_expect_success 'two-way with incorrect --exclude-per-directory (2)' '
+
+       if err=`git read-tree -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1`
+       then
+               echo should have complained
+               false
+       else
+               echo "happy to see $err"
+       fi
+'
+
+test_expect_success 'two-way clobbering a ignored file' '
+
+       git read-tree -m -u --exclude-per-directory=.gitignore master side
+'
+
+rm -f .gitignore
+
 # three-tree test
 
-test_expect_success 'three-way not complaining' '
+test_expect_success 'three-way not complaining on an untracked path in both' '
 
-       rm -f file2 &&
+       rm -f file2 subdir/file2 &&
        git checkout side &&
        echo >file3 file three &&
-       git update-index --add file3 &&
-       git commit -a -m "side adds file3" &&
+       echo >subdir/file3 file three &&
+       git update-index --add file3 subdir/file3 &&
+       git commit -a -m "side adds file3 and removes file2" &&
 
        git checkout master &&
        echo >file2 file two is untracked on the master side &&
+       echo >subdir/file2 file two is untracked on the master side &&
 
        git-read-tree -m -u branch-point master side
 '
 
+test_expect_success 'three-way not cloberring a working tree file' '
+
+       git reset --hard &&
+       rm -f file2 subdir/file2 file3 subdir/file3 &&
+       git checkout master &&
+       echo >file3 file three created in master, untracked &&
+       echo >subdir/file3 file three created in master, untracked &&
+       if err=`git read-tree -m -u branch-point master side 2>&1`
+       then
+               echo should have complained
+               false
+       else
+               echo "happy to see $err"
+       fi
+'
+
+echo >.gitignore file3
+
+test_expect_success 'three-way not complaining on an untracked file' '
+
+       git reset --hard &&
+       rm -f file2 subdir/file2 file3 subdir/file3 &&
+       git checkout master &&
+       echo >file3 file three created in master, untracked &&
+       echo >subdir/file3 file three created in master, untracked &&
+
+       git read-tree -m -u --exclude-per-directory=.gitignore branch-point master side
+'
+
 test_done
index 7cfd628d8e1c576115cb934e72138535fb7d2db3..79d21e2e8dd89f6e0560a2aded03f5bf3eb77f78 100644 (file)
@@ -1,6 +1,7 @@
 #include <signal.h>
 #include <sys/time.h>
 #include "cache.h"
+#include "dir.h"
 #include "tree.h"
 #include "tree-walk.h"
 #include "cache-tree.h"
@@ -77,6 +78,12 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
 {
        int baselen = strlen(base);
        int src_size = len + 1;
+       int i_stk = i_stk;
+       int retval = 0;
+
+       if (o->dir)
+               i_stk = push_exclude_per_directory(o->dir, base, strlen(base));
+
        do {
                int i;
                const char *first;
@@ -143,7 +150,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                }
                /* No name means we're done */
                if (!first)
-                       return 0;
+                       goto leave_directory;
 
                pathlen = strlen(first);
                ce_size = cache_entry_size(baselen + pathlen);
@@ -240,13 +247,20 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                        newbase[baselen + pathlen] = '/';
                        newbase[baselen + pathlen + 1] = '\0';
                        if (unpack_trees_rec(subposns, len, newbase, o,
-                                            indpos, df_conflict_list))
-                               return -1;
+                                            indpos, df_conflict_list)) {
+                               retval = -1;
+                               goto leave_directory;
+                       }
                        free(newbase);
                }
                free(subposns);
                free(src);
        } while (1);
+
+ leave_directory:
+       if (o->dir)
+               pop_exclude_per_directory(o->dir, i_stk);
+       return retval;
 }
 
 /* Unlink the last component and attempt to remove leading
@@ -456,7 +470,7 @@ static void invalidate_ce_path(struct cache_entry *ce)
 
 /*
  * We do not want to remove or overwrite a working tree file that
- * is not tracked.
+ * is not tracked, unless it is ignored.
  */
 static void verify_absent(const char *path, const char *action,
                struct unpack_trees_options *o)
@@ -465,7 +479,7 @@ static void verify_absent(const char *path, const char *action,
 
        if (o->index_only || o->reset || !o->update)
                return;
-       if (!lstat(path, &st))
+       if (!lstat(path, &st) && !(o->dir && excluded(o->dir, path)))
                die("Untracked working tree file '%s' "
                    "would be %s by merge.", path, action);
 }
index c4601621cd71ac6e750c90bd3ac4b426d8e12369..191f7442f10683c8043288eece36f39166fedc95 100644 (file)
@@ -16,6 +16,7 @@ struct unpack_trees_options {
        int verbose_update;
        int aggressive;
        const char *prefix;
+       struct dir_struct *dir;
        merge_fn_t fn;
 
        int head_idx;