From 52a0a1bd10f67ffb6d1eec420efed0899c606988 Mon Sep 17 00:00:00 2001 From: Clemens Buchacher Date: Sun, 10 Oct 2010 10:35:43 +0200 Subject: [PATCH 1/5] t7607: use test-lib functions and check MERGE_HEAD Use the test_commit and test_path_is_missing functions from the test library. Also make sure that a merge which fails due to pre-merge checks aborts properly and does not leave MERGE_HEAD behind. The "will not overwrite removed file" test is an exception to this. It notices the untracked file at a stage where the merge is already well under way. Therefore we cannot abort the merge without major restructuring. See the following thread for more details. http://mid.gmane.org/7vskopwxej.fsf@gitster.siamese.dyndns.org Signed-off-by: Clemens Buchacher --- t/t7607-merge-overwrite.sh | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh index d82349a6a8..b8fab548b2 100755 --- a/t/t7607-merge-overwrite.sh +++ b/t/t7607-merge-overwrite.sh @@ -7,48 +7,38 @@ Do not overwrite changes.' . ./test-lib.sh test_expect_success 'setup' ' - echo c0 > c0.c && - git add c0.c && - git commit -m c0 && - git tag c0 && - echo c1 > c1.c && - git add c1.c && - git commit -m c1 && - git tag c1 && + test_commit c0 c0.c && + test_commit c1 c1.c && + test_commit c1a c1.c "c1 a" && git reset --hard c0 && - echo c2 > c2.c && - git add c2.c && - git commit -m c2 && - git tag c2 && - git reset --hard c1 && - echo "c1 a" > c1.c && - git add c1.c && - git commit -m "c1 a" && - git tag c1a && + test_commit c2 c2.c && echo "VERY IMPORTANT CHANGES" > important ' test_expect_success 'will not overwrite untracked file' ' git reset --hard c1 && - cat important > c2.c && + cp important c2.c && test_must_fail git merge c2 && + test_path_is_missing .git/MERGE_HEAD && test_cmp important c2.c ' test_expect_success 'will not overwrite new file' ' git reset --hard c1 && - cat important > c2.c && + cp important c2.c && git add c2.c && test_must_fail git merge c2 && + test_path_is_missing .git/MERGE_HEAD && test_cmp important c2.c ' test_expect_success 'will not overwrite staged changes' ' git reset --hard c1 && - cat important > c2.c && + cp important c2.c && git add c2.c && rm c2.c && test_must_fail git merge c2 && + test_path_is_missing .git/MERGE_HEAD && git checkout c2.c && test_cmp important c2.c ' @@ -57,7 +47,7 @@ test_expect_success 'will not overwrite removed file' ' git reset --hard c1 && git rm c1.c && git commit -m "rm c1.c" && - cat important > c1.c && + cp important c1.c && test_must_fail git merge c1a && test_cmp important c1.c ' @@ -66,9 +56,10 @@ test_expect_success 'will not overwrite re-added file' ' git reset --hard c1 && git rm c1.c && git commit -m "rm c1.c" && - cat important > c1.c && + cp important c1.c && git add c1.c && test_must_fail git merge c1a && + test_path_is_missing .git/MERGE_HEAD && test_cmp important c1.c ' @@ -76,10 +67,11 @@ test_expect_success 'will not overwrite removed file with staged changes' ' git reset --hard c1 && git rm c1.c && git commit -m "rm c1.c" && - cat important > c1.c && + cp important c1.c && git add c1.c && rm c1.c && test_must_fail git merge c1a && + test_path_is_missing .git/MERGE_HEAD && git checkout c1.c && test_cmp important c1.c ' From 189645ca844da2fd4f7e3318bbd9f7098e886dc4 Mon Sep 17 00:00:00 2001 From: Clemens Buchacher Date: Sun, 10 Oct 2010 10:38:58 +0200 Subject: [PATCH 2/5] t7607: add leading-path tests Signed-off-by: Clemens Buchacher --- t/t7607-merge-overwrite.sh | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh index b8fab548b2..77fcaa2e31 100755 --- a/t/t7607-merge-overwrite.sh +++ b/t/t7607-merge-overwrite.sh @@ -12,6 +12,12 @@ test_expect_success 'setup' ' test_commit c1a c1.c "c1 a" && git reset --hard c0 && test_commit c2 c2.c && + git reset --hard c0 && + mkdir sub && + echo "sub/f" > sub/f && + git add sub/f && + git commit -m sub && + git tag sub && echo "VERY IMPORTANT CHANGES" > important ' @@ -23,6 +29,14 @@ test_expect_success 'will not overwrite untracked file' ' test_cmp important c2.c ' +test_expect_success 'will overwrite tracked file' ' + git reset --hard c1 && + cp important c2.c && + git add c2.c && + git commit -m important && + git checkout c2 +' + test_expect_success 'will not overwrite new file' ' git reset --hard c1 && cp important c2.c && @@ -76,4 +90,41 @@ test_expect_success 'will not overwrite removed file with staged changes' ' test_cmp important c1.c ' +test_expect_success 'will not overwrite untracked subtree' ' + git reset --hard c0 && + rm -rf sub && + mkdir -p sub/f && + cp important sub/f/important && + test_must_fail git merge sub && + test_path_is_missing .git/MERGE_HEAD && + test_cmp important sub/f/important +' + +test_expect_failure 'will not overwrite untracked file in leading path' ' + git reset --hard c0 && + rm -rf sub && + cp important sub && + test_must_fail git merge sub && + test_path_is_missing .git/MERGE_HEAD && + test_cmp important sub +' + +test_expect_failure SYMLINKS 'will not overwrite untracked symlink in leading path' ' + git reset --hard c0 && + rm -rf sub && + mkdir sub2 && + ln -s sub2 sub && + test_must_fail git merge sub && + test_path_is_missing .git/MERGE_HEAD +' + +test_expect_success SYMLINKS 'will not be confused by symlink in leading path' ' + git reset --hard c0 && + rm -rf sub && + ln -s sub2 sub && + git add sub && + git commit -m ln && + git checkout sub +' + test_done From a9307f5a6880a4818b042a30760b2cbb75efc53b Mon Sep 17 00:00:00 2001 From: Clemens Buchacher Date: Sat, 9 Oct 2010 15:52:58 +0200 Subject: [PATCH 3/5] add function check_ok_to_remove() This wraps some inline code into the function check_ok_to_remove(), which will later be used for leading path components as well. Signed-off-by: Clemens Buchacher --- unpack-trees.c | 107 +++++++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 49 deletions(-) diff --git a/unpack-trees.c b/unpack-trees.c index 803445aa7b..df1c9209d4 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1127,14 +1127,65 @@ static int verify_clean_subdirectory(struct cache_entry *ce, * See if we can find a case-insensitive match in the index that also * matches the stat information, and assume it's that other file! */ -static int icase_exists(struct unpack_trees_options *o, struct cache_entry *dst, struct stat *st) +static int icase_exists(struct unpack_trees_options *o, const char *name, int len, struct stat *st) { struct cache_entry *src; - src = index_name_exists(o->src_index, dst->name, ce_namelen(dst), 1); + src = index_name_exists(o->src_index, name, len, 1); return src && !ie_match_stat(o->src_index, src, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); } +static int check_ok_to_remove(const char *name, int len, int dtype, + struct cache_entry *ce, struct stat *st, + enum unpack_trees_error_types error_type, + struct unpack_trees_options *o) +{ + struct cache_entry *result; + + /* + * It may be that the 'lstat()' succeeded even though + * target 'ce' was absent, because there is an old + * entry that is different only in case.. + * + * Ignore that lstat() if it matches. + */ + if (ignore_case && icase_exists(o, name, len, st)) + return 0; + + if (o->dir && excluded(o->dir, name, &dtype)) + /* + * ce->name is explicitly excluded, so it is Ok to + * overwrite it. + */ + return 0; + 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 + * them. + */ + if (verify_clean_subdirectory(ce, error_type, o) < 0) + return -1; + return 0; + } + + /* + * The previous round may already have decided to + * delete this path, which is in a subdirectory that + * is being replaced with a blob. + */ + result = index_name_exists(&o->result, name, len, 0); + if (result) { + if (result->ce_flags & CE_REMOVE) + return 0; + } + + return o->gently ? -1 : + add_rejected_path(o, error_type, name); +} + /* * We do not want to remove or overwrite a working tree file that * is not tracked, unless it is ignored. @@ -1151,55 +1202,13 @@ static int verify_absent_1(struct cache_entry *ce, if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce))) return 0; - if (!lstat(ce->name, &st)) { - int dtype = ce_to_dtype(ce); - struct cache_entry *result; - - /* - * It may be that the 'lstat()' succeeded even though - * target 'ce' was absent, because there is an old - * entry that is different only in case.. - * - * Ignore that lstat() if it matches. - */ - if (ignore_case && icase_exists(o, ce, &st)) - return 0; - - if (o->dir && excluded(o->dir, ce->name, &dtype)) - /* - * ce->name is explicitly excluded, so it is Ok to - * overwrite it. - */ - return 0; - 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 - * them. - */ - if (verify_clean_subdirectory(ce, error_type, o) < 0) - return -1; - return 0; - } - - /* - * The previous round may already have decided to - * delete this path, which is in a subdirectory that - * is being replaced with a blob. - */ - result = index_name_exists(&o->result, ce->name, ce_namelen(ce), 0); - if (result) { - if (result->ce_flags & CE_REMOVE) - return 0; - } - - return o->gently ? -1 : - add_rejected_path(o, error_type, ce->name); - } + if (!lstat(ce->name, &st)) + return check_ok_to_remove(ce->name, ce_namelen(ce), + ce_to_dtype(ce), ce, &st, + error_type, o); return 0; } + static int verify_absent(struct cache_entry *ce, enum unpack_trees_error_types error_type, struct unpack_trees_options *o) From 4856ff2a195e558c82ca3cab636c4d4fbef148e3 Mon Sep 17 00:00:00 2001 From: Clemens Buchacher Date: Sat, 9 Oct 2010 15:52:59 +0200 Subject: [PATCH 4/5] lstat_cache: optionally return match_len Return match_len so that the caller can know which leading path component matched. Signed-off-by: Clemens Buchacher --- symlinks.c | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/symlinks.c b/symlinks.c index 8860120011..b7343ebd62 100644 --- a/symlinks.c +++ b/symlinks.c @@ -64,11 +64,13 @@ static inline void reset_lstat_cache(struct cache_def *cache) * of the prefix, where the cache should use the stat() function * instead of the lstat() function to test each path component. */ -static int lstat_cache(struct cache_def *cache, const char *name, int len, - int track_flags, int prefix_len_stat_func) +static int lstat_cache_matchlen(struct cache_def *cache, + const char *name, int len, + int *ret_flags, int track_flags, + int prefix_len_stat_func) { int match_len, last_slash, last_slash_dir, previous_slash; - int match_flags, ret_flags, save_flags, max_len, ret; + int save_flags, max_len, ret; struct stat st; if (cache->track_flags != track_flags || @@ -90,13 +92,13 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len, match_len = last_slash = longest_path_match(name, len, cache->path, cache->len, &previous_slash); - match_flags = cache->flags & track_flags & (FL_NOENT|FL_SYMLINK); + *ret_flags = cache->flags & track_flags & (FL_NOENT|FL_SYMLINK); if (!(track_flags & FL_FULLPATH) && match_len == len) match_len = last_slash = previous_slash; - if (match_flags && match_len == cache->len) - return match_flags; + if (*ret_flags && match_len == cache->len) + return match_len; /* * If we now have match_len > 0, we would know that * the matched part will always be a directory. @@ -105,16 +107,16 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len, * a substring of the cache on a path component basis, * we can return immediately. */ - match_flags = track_flags & FL_DIR; - if (match_flags && len == match_len) - return match_flags; + *ret_flags = track_flags & FL_DIR; + if (*ret_flags && len == match_len) + return match_len; } /* * Okay, no match from the cache so far, so now we have to * check the rest of the path components. */ - ret_flags = FL_DIR; + *ret_flags = FL_DIR; last_slash_dir = last_slash; max_len = len < PATH_MAX ? len : PATH_MAX; while (match_len < max_len) { @@ -133,16 +135,16 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len, ret = lstat(cache->path, &st); if (ret) { - ret_flags = FL_LSTATERR; + *ret_flags = FL_LSTATERR; if (errno == ENOENT) - ret_flags |= FL_NOENT; + *ret_flags |= FL_NOENT; } else if (S_ISDIR(st.st_mode)) { last_slash_dir = last_slash; continue; } else if (S_ISLNK(st.st_mode)) { - ret_flags = FL_SYMLINK; + *ret_flags = FL_SYMLINK; } else { - ret_flags = FL_ERR; + *ret_flags = FL_ERR; } break; } @@ -152,7 +154,7 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len, * path types, FL_NOENT, FL_SYMLINK and FL_DIR, can be cached * for the moment! */ - save_flags = ret_flags & track_flags & (FL_NOENT|FL_SYMLINK); + save_flags = *ret_flags & track_flags & (FL_NOENT|FL_SYMLINK); if (save_flags && last_slash > 0 && last_slash <= PATH_MAX) { cache->path[last_slash] = '\0'; cache->len = last_slash; @@ -176,7 +178,16 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len, } else { reset_lstat_cache(cache); } - return ret_flags; + return match_len; +} + +static int lstat_cache(struct cache_def *cache, const char *name, int len, + int track_flags, int prefix_len_stat_func) +{ + int flags; + (void)lstat_cache_matchlen(cache, name, len, &flags, track_flags, + prefix_len_stat_func); + return flags; } #define USE_ONLY_LSTAT 0 From f66caaf9c8e0feb840a439a58e165b963aec79cf Mon Sep 17 00:00:00 2001 From: Clemens Buchacher Date: Sat, 9 Oct 2010 15:53:00 +0200 Subject: [PATCH 5/5] do not overwrite files in leading path If the work tree contains an untracked file x, and unpack-trees wants to checkout a path x/*, the file x is removed unconditionally. Instead, apply the same checks that are normally used for untracked files, and abort if the file cannot be removed. Signed-off-by: Clemens Buchacher --- cache.h | 2 +- symlinks.c | 21 ++++++++++++++++----- t/t7607-merge-overwrite.sh | 2 +- unpack-trees.c | 16 +++++++++++++--- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/cache.h b/cache.h index 3d5ed51989..5cfee34e2f 100644 --- a/cache.h +++ b/cache.h @@ -859,7 +859,7 @@ struct cache_def { extern int has_symlink_leading_path(const char *name, int len); extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int); -extern int has_symlink_or_noent_leading_path(const char *name, int len); +extern int check_leading_path(const char *name, int len); extern int has_dirs_only_path(const char *name, int len, int prefix_len); extern void schedule_dir_for_removal(const char *name, int len); extern void remove_scheduled_dirs(void); diff --git a/symlinks.c b/symlinks.c index b7343ebd62..3cacebd91a 100644 --- a/symlinks.c +++ b/symlinks.c @@ -209,15 +209,26 @@ int has_symlink_leading_path(const char *name, int len) } /* - * Return non-zero if path 'name' has a leading symlink component or + * Return zero if path 'name' has a leading symlink component or * if some leading path component does not exists. + * + * Return -1 if leading path exists and is a directory. + * + * Return path length if leading path exists and is neither a + * directory nor a symlink. */ -int has_symlink_or_noent_leading_path(const char *name, int len) +int check_leading_path(const char *name, int len) { struct cache_def *cache = &default_cache; /* FIXME */ - return lstat_cache(cache, name, len, - FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT) & - (FL_SYMLINK|FL_NOENT); + int flags; + int match_len = lstat_cache_matchlen(cache, name, len, &flags, + FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT); + if (flags & (FL_SYMLINK|FL_NOENT)) + return 0; + else if (flags & FL_DIR) + return -1; + else + return match_len; } /* diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh index 77fcaa2e31..e49dd8023c 100755 --- a/t/t7607-merge-overwrite.sh +++ b/t/t7607-merge-overwrite.sh @@ -100,7 +100,7 @@ test_expect_success 'will not overwrite untracked subtree' ' test_cmp important sub/f/important ' -test_expect_failure 'will not overwrite untracked file in leading path' ' +test_expect_success 'will not overwrite untracked file in leading path' ' git reset --hard c0 && rm -rf sub && cp important sub && diff --git a/unpack-trees.c b/unpack-trees.c index df1c9209d4..6816113420 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -182,7 +182,7 @@ static void display_error_msgs(struct unpack_trees_options *o) */ static void unlink_entry(struct cache_entry *ce) { - if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce))) + if (!check_leading_path(ce->name, ce_namelen(ce))) return; if (remove_or_warn(ce->ce_mode, ce->name)) return; @@ -1194,18 +1194,28 @@ static int verify_absent_1(struct cache_entry *ce, enum unpack_trees_error_types error_type, struct unpack_trees_options *o) { + int len; struct stat st; if (o->index_only || o->reset || !o->update) return 0; - if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce))) + len = check_leading_path(ce->name, ce_namelen(ce)); + if (!len) return 0; + else if (len > 0) { + char path[PATH_MAX + 1]; + memcpy(path, ce->name, len); + path[len] = 0; + lstat(path, &st); - if (!lstat(ce->name, &st)) + return check_ok_to_remove(path, len, DT_UNKNOWN, NULL, &st, + error_type, o); + } else if (!lstat(ce->name, &st)) return check_ok_to_remove(ce->name, ce_namelen(ce), ce_to_dtype(ce), ce, &st, error_type, o); + return 0; }