Merge branch 'jc/read-tree-ignore'

* jc/read-tree-ignore:
  read-tree: document --exclude-per-directory
  Loosen "working file will be lost" check in Porcelain-ish
  read-tree: further loosen "working file will be lost" check.
This commit is contained in:
Junio C Hamano
2006-12-13 11:10:24 -08:00
9 changed files with 139 additions and 20 deletions

View File

@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index
SYNOPSIS SYNOPSIS
-------- --------
'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]]) 'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
DESCRIPTION DESCRIPTION
@ -71,6 +71,20 @@ OPTIONS
directory. Note that the `<prefix>/` value must end directory. Note that the `<prefix>/` value must end
with a slash. with a slash.
--exclude-per-directory=<gitignore>::
When running the command with `-u` and `-m` options, the
merge result may need to overwrite paths that are not
tracked in the current branch. The command usually
refuses to proceed with the merge to avoid losing such a
path. However this safety valve sometimes gets in the
way. For example, it often happens that the other
branch added a file that used to be a generated file in
your branch, and the safety valve triggers when you try
to switch to that branch after you ran `make` but before
running `make clean` to remove the generated file. This
option tells the command to read per-directory exclude
file (usually '.gitignore') and allows such an untracked
but explicitly ignored file to be overwritten.
<tree-ish#>:: <tree-ish#>::
The id of the tree object(s) to be read/merged. The id of the tree object(s) to be read/merged.

View File

@ -10,6 +10,7 @@
#include "tree-walk.h" #include "tree-walk.h"
#include "cache-tree.h" #include "cache-tree.h"
#include "unpack-trees.h" #include "unpack-trees.h"
#include "dir.h"
#include "builtin.h" #include "builtin.h"
static struct object_list *trees; static struct object_list *trees;
@ -84,7 +85,7 @@ static void prime_cache_tree(void)
} }
static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <sha1> [<sha2> [<sha3>]])"; static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] <sha1> [<sha2> [<sha3>]])";
static struct lock_file lock_file; static struct lock_file lock_file;
@ -178,6 +179,23 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
continue; 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 */ /* using -u and -i at the same time makes no sense */
if (1 < opts.index_only + opts.update) if (1 < opts.index_only + opts.update)
usage(read_tree_usage); 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) if ((opts.update||opts.index_only) && !opts.merge)
usage(read_tree_usage); usage(read_tree_usage);
if ((opts.dir && !opts.update))
die("--exclude-per-directory is meaningless unless -u");
if (opts.prefix) { if (opts.prefix) {
int pfxlen = strlen(opts.prefix); int pfxlen = strlen(opts.prefix);

4
dir.c
View File

@ -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); 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]; char exclude_file[PATH_MAX];
struct exclude_list *el = &dir->exclude_list[EXC_DIRS]; 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; 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]; struct exclude_list *el = &dir->exclude_list[EXC_DIRS];

3
dir.h
View File

@ -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 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 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 int excluded(struct dir_struct *, const char *);
extern void add_excludes_from_file(struct dir_struct *, const char *fname); extern void add_excludes_from_file(struct dir_struct *, const char *fname);
extern void add_exclude(const char *string, const char *base, extern void add_exclude(const char *string, const char *base,

View File

@ -161,7 +161,7 @@ then
git-read-tree --reset -u $new git-read-tree --reset -u $new
else else
git-update-index --refresh >/dev/null git-update-index --refresh >/dev/null
merge_error=$(git-read-tree -m -u $old $new 2>&1) || ( merge_error=$(git-read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || (
case "$merge" in case "$merge" in
'') '')
echo >&2 "$merge_error" echo >&2 "$merge_error"
@ -172,7 +172,8 @@ else
git diff-files --name-only | git update-index --remove --stdin && git diff-files --name-only | git update-index --remove --stdin &&
work=`git write-tree` && work=`git write-tree` &&
git read-tree --reset -u $new && git read-tree --reset -u $new &&
git read-tree -m -u --aggressive $old $new $work || exit git read-tree -m -u --aggressive --exclude-per-directory=.gitignore $old $new $work ||
exit
if result=`git write-tree 2>/dev/null` if result=`git write-tree 2>/dev/null`
then then

View File

@ -265,7 +265,7 @@ f,*)
echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)" echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)"
git-update-index --refresh 2>/dev/null git-update-index --refresh 2>/dev/null
new_head=$(git-rev-parse --verify "$1^0") && new_head=$(git-rev-parse --verify "$1^0") &&
git-read-tree -u -v -m $head "$new_head" && git-read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" &&
finish "$new_head" "Fast forward" finish "$new_head" "Fast forward"
dropsave dropsave
exit 0 exit 0

View File

@ -8,23 +8,27 @@ test_description='read-tree -m -u checks working tree files'
test_expect_success 'two-way setup' ' test_expect_success 'two-way setup' '
mkdir subdir &&
echo >file1 file one && echo >file1 file one &&
echo >file2 file two && 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 commit -m initial &&
git branch side && git branch side &&
git tag -f branch-point && git tag -f branch-point &&
echo file2 is not tracked on the master anymore && echo file2 is not tracked on the master anymore &&
rm -f file2 && rm -f file2 subdir/file2 &&
git update-index --remove file2 && git update-index --remove file2 subdir/file2 &&
git commit -a -m "master removes file2" git commit -a -m "master removes file2 and subdir/file2"
' '
test_expect_success 'two-way not clobbering' ' test_expect_success 'two-way not clobbering' '
echo >file2 master creates untracked file2 && 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` if err=`git read-tree -m -u master side 2>&1`
then then
echo should have complained echo should have complained
@ -34,20 +38,82 @@ test_expect_success 'two-way not clobbering' '
fi 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 # 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 && git checkout side &&
echo >file3 file three && echo >file3 file three &&
git update-index --add file3 && echo >subdir/file3 file three &&
git commit -a -m "side adds file3" && git update-index --add file3 subdir/file3 &&
git commit -a -m "side adds file3 and removes file2" &&
git checkout master && git checkout master &&
echo >file2 file two is untracked on the master side && 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 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 test_done

View File

@ -1,6 +1,7 @@
#include <signal.h> #include <signal.h>
#include <sys/time.h> #include <sys/time.h>
#include "cache.h" #include "cache.h"
#include "dir.h"
#include "tree.h" #include "tree.h"
#include "tree-walk.h" #include "tree-walk.h"
#include "cache-tree.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 baselen = strlen(base);
int src_size = len + 1; 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 { do {
int i; int i;
const char *first; 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 */ /* No name means we're done */
if (!first) if (!first)
return 0; goto leave_directory;
pathlen = strlen(first); pathlen = strlen(first);
ce_size = cache_entry_size(baselen + pathlen); 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] = '/';
newbase[baselen + pathlen + 1] = '\0'; newbase[baselen + pathlen + 1] = '\0';
if (unpack_trees_rec(subposns, len, newbase, o, if (unpack_trees_rec(subposns, len, newbase, o,
indpos, df_conflict_list)) indpos, df_conflict_list)) {
return -1; retval = -1;
goto leave_directory;
}
free(newbase); free(newbase);
} }
free(subposns); free(subposns);
free(src); free(src);
} while (1); } 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 /* Unlink the last component and attempt to remove leading
@ -458,7 +472,7 @@ static void invalidate_ce_path(struct cache_entry *ce)
/* /*
* We do not want to remove or overwrite a working tree file that * 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, static void verify_absent(const char *path, const char *action,
struct unpack_trees_options *o) struct unpack_trees_options *o)
@ -467,7 +481,7 @@ static void verify_absent(const char *path, const char *action,
if (o->index_only || o->reset || !o->update) if (o->index_only || o->reset || !o->update)
return; return;
if (!lstat(path, &st)) if (!lstat(path, &st) && !(o->dir && excluded(o->dir, path)))
die("Untracked working tree file '%s' " die("Untracked working tree file '%s' "
"would be %s by merge.", path, action); "would be %s by merge.", path, action);
} }

View File

@ -16,6 +16,7 @@ struct unpack_trees_options {
int verbose_update; int verbose_update;
int aggressive; int aggressive;
const char *prefix; const char *prefix;
struct dir_struct *dir;
merge_fn_t fn; merge_fn_t fn;
int head_idx; int head_idx;