diff --git a/builtin-read-tree.c b/builtin-read-tree.c index c1867d2a00..3f6cae36ce 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -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 96389b32e6..e6a61ee913 100644 --- 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 313f8ab64e..550551ab25 100644 --- 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, diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh index 018fbea450..4f664f6adf 100755 --- a/t/t1004-read-tree-m-u-wf.sh +++ b/t/t1004-read-tree-m-u-wf.sh @@ -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 diff --git a/unpack-trees.c b/unpack-trees.c index 7cfd628d8e..79d21e2e8d 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1,6 +1,7 @@ #include #include #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); } diff --git a/unpack-trees.h b/unpack-trees.h index c4601621cd..191f7442f1 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -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;