From a0cc58450a8ac81ba405f1e161599263d1678686 Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Thu, 20 Dec 2018 13:48:13 +0000 Subject: [PATCH 1/9] move worktree tests to t24* The 'git worktree' command used to be just another mode in 'git checkout', namely 'git checkout --to'. When the tests for the latter were retrofitted for the former, the test name was adjusted, but the test number was kept, even though the test is testing a different command now. t/README states: "Second digit tells the particular command we are testing.", so 'git worktree' should have a separate number just for itself. Move the worktree tests to t24* to adhere to that guideline. We're going to make use of the free'd up numbers in a subsequent commit. Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- t/{t2025-worktree-add.sh => t2400-worktree-add.sh} | 0 t/{t2026-worktree-prune.sh => t2401-worktree-prune.sh} | 0 t/{t2027-worktree-list.sh => t2402-worktree-list.sh} | 0 t/{t2028-worktree-move.sh => t2403-worktree-move.sh} | 0 t/{t2029-worktree-config.sh => t2404-worktree-config.sh} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename t/{t2025-worktree-add.sh => t2400-worktree-add.sh} (100%) rename t/{t2026-worktree-prune.sh => t2401-worktree-prune.sh} (100%) rename t/{t2027-worktree-list.sh => t2402-worktree-list.sh} (100%) rename t/{t2028-worktree-move.sh => t2403-worktree-move.sh} (100%) rename t/{t2029-worktree-config.sh => t2404-worktree-config.sh} (100%) diff --git a/t/t2025-worktree-add.sh b/t/t2400-worktree-add.sh similarity index 100% rename from t/t2025-worktree-add.sh rename to t/t2400-worktree-add.sh diff --git a/t/t2026-worktree-prune.sh b/t/t2401-worktree-prune.sh similarity index 100% rename from t/t2026-worktree-prune.sh rename to t/t2401-worktree-prune.sh diff --git a/t/t2027-worktree-list.sh b/t/t2402-worktree-list.sh similarity index 100% rename from t/t2027-worktree-list.sh rename to t/t2402-worktree-list.sh diff --git a/t/t2028-worktree-move.sh b/t/t2403-worktree-move.sh similarity index 100% rename from t/t2028-worktree-move.sh rename to t/t2403-worktree-move.sh diff --git a/t/t2029-worktree-config.sh b/t/t2404-worktree-config.sh similarity index 100% rename from t/t2029-worktree-config.sh rename to t/t2404-worktree-config.sh From b702dd12d52816e192578c6206db5e6c332ba49b Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Thu, 20 Dec 2018 13:48:14 +0000 Subject: [PATCH 2/9] entry: factor out unlink_entry function Factor out the 'unlink_entry()' function from unpack-trees.c to entry.c. It will be used in other places as well in subsequent steps. As it's no longer a static function, also move the documentation to the header file to make it more discoverable. Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- cache.h | 5 +++++ entry.c | 15 +++++++++++++++ unpack-trees.c | 19 ------------------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/cache.h b/cache.h index ca36b44ee0..c1c953e810 100644 --- a/cache.h +++ b/cache.h @@ -1542,6 +1542,11 @@ struct checkout { extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath); extern void enable_delayed_checkout(struct checkout *state); extern int finish_delayed_checkout(struct checkout *state); +/* + * Unlink the last component and schedule the leading directories for + * removal, such that empty directories get removed. + */ +extern void unlink_entry(const struct cache_entry *ce); struct cache_def { struct strbuf path; diff --git a/entry.c b/entry.c index 0a3c451f5f..b9eef57117 100644 --- a/entry.c +++ b/entry.c @@ -508,3 +508,18 @@ int checkout_entry(struct cache_entry *ce, create_directories(path.buf, path.len, state); return write_entry(ce, path.buf, state, 0); } + +void unlink_entry(const struct cache_entry *ce) +{ + const struct submodule *sub = submodule_from_ce(ce); + if (sub) { + /* state.force is set at the caller. */ + submodule_move_head(ce->name, "HEAD", NULL, + SUBMODULE_MOVE_HEAD_FORCE); + } + if (!check_leading_path(ce->name, ce_namelen(ce))) + return; + if (remove_or_warn(ce->ce_mode, ce->name)) + return; + schedule_dir_for_removal(ce->name, ce_namelen(ce)); +} diff --git a/unpack-trees.c b/unpack-trees.c index 7570df481b..e8d1a6ac50 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -300,25 +300,6 @@ static void load_gitmodules_file(struct index_state *index, } } -/* - * Unlink the last component and schedule the leading directories for - * removal, such that empty directories get removed. - */ -static void unlink_entry(const struct cache_entry *ce) -{ - const struct submodule *sub = submodule_from_ce(ce); - if (sub) { - /* state.force is set at the caller. */ - submodule_move_head(ce->name, "HEAD", NULL, - SUBMODULE_MOVE_HEAD_FORCE); - } - if (!check_leading_path(ce->name, ce_namelen(ce))) - return; - if (remove_or_warn(ce->ce_mode, ce->name)) - return; - schedule_dir_for_removal(ce->name, ce_namelen(ce)); -} - static struct progress *get_progress(struct unpack_trees_options *o) { unsigned cnt = 0, total = 0; From 536ec1839dbde8b9a6b38e6ccb5ab01b2b6311f9 Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Thu, 20 Dec 2018 13:48:15 +0000 Subject: [PATCH 3/9] entry: support CE_WT_REMOVE flag in checkout_entry 'checkout_entry()' currently only supports creating new entries in the working tree, but not deleting them. Add the ability to remove entries at the same time if the entry is marked with the CE_WT_REMOVE flag. Currently this doesn't have any effect, as the CE_WT_REMOVE flag is only used in unpack-tree, however we will make use of this in a subsequent step in the series. Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- entry.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/entry.c b/entry.c index b9eef57117..3d3701e7ae 100644 --- a/entry.c +++ b/entry.c @@ -441,6 +441,17 @@ int checkout_entry(struct cache_entry *ce, static struct strbuf path = STRBUF_INIT; struct stat st; + if (ce->ce_flags & CE_WT_REMOVE) { + if (topath) + /* + * No content and thus no path to create, so we have + * no pathname to return. + */ + BUG("Can't remove entry to a path"); + unlink_entry(ce); + return 0; + } + if (topath) return write_entry(ce, topath, state, 1); From 6fdc2057225ad1ae735ecaacdcace77c8b0b6b76 Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Thu, 20 Dec 2018 13:48:16 +0000 Subject: [PATCH 4/9] read-cache: add invalidate parameter to remove_marked_cache_entries When marking cache entries for removal, and later removing them all at once using 'remove_marked_cache_entries()', cache entries currently have to be invalidated manually in the cache tree and in the untracked cache. Add an invalidate flag to the function. With the flag set, the function will take care of invalidating the path in the cache tree and in the untracked cache. Note that the current callsites already do the invalidation properly in other places, so we're just passing 0 from there to keep the status quo. This will be useful in a subsequent commit. Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- cache.h | 2 +- read-cache.c | 8 +++++++- split-index.c | 2 +- unpack-trees.c | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cache.h b/cache.h index c1c953e810..1deee48f5b 100644 --- a/cache.h +++ b/cache.h @@ -751,7 +751,7 @@ extern void rename_index_entry_at(struct index_state *, int pos, const char *new /* Remove entry, return true if there are more entries to go. */ extern int remove_index_entry_at(struct index_state *, int pos); -extern void remove_marked_cache_entries(struct index_state *istate); +extern void remove_marked_cache_entries(struct index_state *istate, int invalidate); extern int remove_file_from_index(struct index_state *, const char *path); #define ADD_CACHE_VERBOSE 1 #define ADD_CACHE_PRETEND 2 diff --git a/read-cache.c b/read-cache.c index bd45dc3e24..978d43f676 100644 --- a/read-cache.c +++ b/read-cache.c @@ -590,13 +590,19 @@ int remove_index_entry_at(struct index_state *istate, int pos) * CE_REMOVE is set in ce_flags. This is much more effective than * calling remove_index_entry_at() for each entry to be removed. */ -void remove_marked_cache_entries(struct index_state *istate) +void remove_marked_cache_entries(struct index_state *istate, int invalidate) { struct cache_entry **ce_array = istate->cache; unsigned int i, j; for (i = j = 0; i < istate->cache_nr; i++) { if (ce_array[i]->ce_flags & CE_REMOVE) { + if (invalidate) { + cache_tree_invalidate_path(istate, + ce_array[i]->name); + untracked_cache_remove_from_index(istate, + ce_array[i]->name); + } remove_name_hash(istate, ce_array[i]); save_or_free_index_entry(istate, ce_array[i]); } diff --git a/split-index.c b/split-index.c index 5820412dc5..8aebc3661b 100644 --- a/split-index.c +++ b/split-index.c @@ -162,7 +162,7 @@ void merge_base_index(struct index_state *istate) ewah_each_bit(si->replace_bitmap, replace_entry, istate); ewah_each_bit(si->delete_bitmap, mark_entry_for_delete, istate); if (si->nr_deletions) - remove_marked_cache_entries(istate); + remove_marked_cache_entries(istate, 0); for (i = si->nr_replacements; i < si->saved_cache_nr; i++) { if (!ce_namelen(si->saved_cache[i])) diff --git a/unpack-trees.c b/unpack-trees.c index e8d1a6ac50..8e6afa924d 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -392,7 +392,7 @@ static int check_updates(struct unpack_trees_options *o) unlink_entry(ce); } } - remove_marked_cache_entries(index); + remove_marked_cache_entries(index, 0); remove_scheduled_dirs(); if (should_update_submodules() && o->update && !o->dry_run) From 5160fa05623dc2e9e8348d48b91f1f0bf44d369e Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Thu, 20 Dec 2018 13:48:17 +0000 Subject: [PATCH 5/9] checkout: clarify comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The key point for the if statement is that read_tree_some did not update the entry, because either it doesn't exist in tree-ish or doesn't match the pathspec. Clarify that. Suggested-by: Nguyễn Thái Ngọc Duy Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- builtin/checkout.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin/checkout.c b/builtin/checkout.c index acdafc6e4c..cb166b2e07 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -304,10 +304,10 @@ static int checkout_paths(const struct checkout_opts *opts, continue; if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) /* - * "git checkout tree-ish -- path", but this entry - * is in the original index; it will not be checked - * out to the working tree and it does not matter - * if pathspec matched this entry. We will not do + * "git checkout tree-ish -- path" and this entry + * is in the original index, but is not in tree-ish + * or does not match the pathspec; it will not be + * checked out to the working tree. We will not do * anything to this entry at all. */ continue; From b7033e73b7b671670160726f62ac16d88161e3d0 Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Thu, 20 Dec 2018 13:48:18 +0000 Subject: [PATCH 6/9] checkout: factor out mark_cache_entry_for_checkout function Factor out the code that marks a cache entry as matched for checkout into a separate function. We are going to introduce a new mode in 'git checkout' in a subsequent commit, that is going to have a slightly different logic. This would make this code unnecessarily complex. Moving that complexity into separate functions will make the code in the subsequent step easier to follow. Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- builtin/checkout.c | 67 +++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/builtin/checkout.c b/builtin/checkout.c index cb166b2e07..32c4b7f897 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -247,6 +247,40 @@ static int checkout_merged(int pos, const struct checkout *state) return status; } +static void mark_ce_for_checkout(struct cache_entry *ce, + char *ps_matched, + const struct checkout_opts *opts) +{ + ce->ce_flags &= ~CE_MATCHED; + if (!opts->ignore_skipworktree && ce_skip_worktree(ce)) + return; + if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) + /* + * "git checkout tree-ish -- path", but this entry + * is in the original index but is not in tree-ish + * or does not match the pathspec; it will not be + * checked out to the working tree. We will not do + * anything to this entry at all. + */ + return; + /* + * Either this entry came from the tree-ish we are + * checking the paths out of, or we are checking out + * of the index. + * + * If it comes from the tree-ish, we already know it + * matches the pathspec and could just stamp + * CE_MATCHED to it from update_some(). But we still + * need ps_matched and read_tree_recursive (and + * eventually tree_entry_interesting) cannot fill + * ps_matched yet. Once it can, we can avoid calling + * match_pathspec() for _all_ entries when + * opts->source_tree != NULL. + */ + if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched)) + ce->ce_flags |= CE_MATCHED; +} + static int checkout_paths(const struct checkout_opts *opts, const char *revision) { @@ -297,37 +331,8 @@ static int checkout_paths(const struct checkout_opts *opts, * Make sure all pathspecs participated in locating the paths * to be checked out. */ - for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; - ce->ce_flags &= ~CE_MATCHED; - if (!opts->ignore_skipworktree && ce_skip_worktree(ce)) - continue; - if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) - /* - * "git checkout tree-ish -- path" and this entry - * is in the original index, but is not in tree-ish - * or does not match the pathspec; it will not be - * checked out to the working tree. We will not do - * anything to this entry at all. - */ - continue; - /* - * Either this entry came from the tree-ish we are - * checking the paths out of, or we are checking out - * of the index. - * - * If it comes from the tree-ish, we already know it - * matches the pathspec and could just stamp - * CE_MATCHED to it from update_some(). But we still - * need ps_matched and read_tree_recursive (and - * eventually tree_entry_interesting) cannot fill - * ps_matched yet. Once it can, we can avoid calling - * match_pathspec() for _all_ entries when - * opts->source_tree != NULL. - */ - if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched)) - ce->ce_flags |= CE_MATCHED; - } + for (pos = 0; pos < active_nr; pos++) + mark_ce_for_checkout(active_cache[pos], ps_matched, opts); if (report_path_error(ps_matched, &opts->pathspec, opts->prefix)) { free(ps_matched); From 091e04bc8cbb0c89c8112c4784f02a44decc257e Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Tue, 8 Jan 2019 21:52:24 +0000 Subject: [PATCH 7/9] checkout: introduce --{,no-}overlay option Currently 'git checkout' is defined as an overlay operation, which means that if in 'git checkout -- []' we have an entry in the index that matches , but that doesn't exist in , that entry will not be removed from the index or the working tree. Introduce a new --{,no-}overlay option, which allows using 'git checkout' in non-overlay mode, thus removing files from the working tree if they do not exist in but match . Note that 'git checkout -p -- []' already works this way, so no changes are needed for the patch mode. We disallow 'git checkout --overlay -p' to avoid confusing users who would expect to be able to force overlay mode in 'git checkout -p' this way. Untracked files are not affected by this change, so 'git checkout --no-overlay HEAD -- untracked' will not remove untracked from the working tree. This is so e.g. 'git checkout --no-overlay HEAD -- dir/' doesn't delete all untracked files in dir/, but rather just resets the state of files that are known to git. Suggested-by: Junio C Hamano Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- Documentation/git-checkout.txt | 10 ++++++ builtin/checkout.c | 66 +++++++++++++++++++++++++++++----- t/t2025-checkout-no-overlay.sh | 47 ++++++++++++++++++++++++ t/t9902-completion.sh | 1 + 4 files changed, 116 insertions(+), 8 deletions(-) create mode 100755 t/t2025-checkout-no-overlay.sh diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 801de2f764..24e52b01e1 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -260,6 +260,9 @@ the conflicted merge in the specified paths. This means that you can use `git checkout -p` to selectively discard edits from your current working tree. See the ``Interactive Mode'' section of linkgit:git-add[1] to learn how to operate the `--patch` mode. ++ +Note that this option uses the no overlay mode by default (see also +`--[no-]overlay`), and currently doesn't support overlay mode. --ignore-other-worktrees:: `git checkout` refuses when the wanted ref is already checked @@ -276,6 +279,13 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode. Just like linkgit:git-submodule[1], this will detach the submodules HEAD. +--[no-]overlay:: + In the default overlay mode, `git checkout` never + removes files from the index or the working tree. When + specifying `--no-overlay`, files that appear in the index and + working tree, but not in are removed, to make them + match exactly. + :: Branch to checkout; if it refers to a branch (i.e., a name that, when prepended with "refs/heads/", is a valid ref), then that diff --git a/builtin/checkout.c b/builtin/checkout.c index 32c4b7f897..0c5fe948ef 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -44,6 +44,7 @@ struct checkout_opts { int ignore_skipworktree; int ignore_other_worktrees; int show_progress; + int overlay_mode; /* * If new checkout options are added, skip_merge_working_tree * should be updated accordingly. @@ -132,7 +133,8 @@ static int skip_same_name(const struct cache_entry *ce, int pos) return pos; } -static int check_stage(int stage, const struct cache_entry *ce, int pos) +static int check_stage(int stage, const struct cache_entry *ce, int pos, + int overlay_mode) { while (pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) { @@ -140,6 +142,8 @@ static int check_stage(int stage, const struct cache_entry *ce, int pos) return 0; pos++; } + if (!overlay_mode) + return 0; if (stage == 2) return error(_("path '%s' does not have our version"), ce->name); else @@ -165,7 +169,7 @@ static int check_stages(unsigned stages, const struct cache_entry *ce, int pos) } static int checkout_stage(int stage, const struct cache_entry *ce, int pos, - const struct checkout *state) + const struct checkout *state, int overlay_mode) { while (pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) { @@ -173,6 +177,10 @@ static int checkout_stage(int stage, const struct cache_entry *ce, int pos, return checkout_entry(active_cache[pos], state, NULL); pos++; } + if (!overlay_mode) { + unlink_entry(ce); + return 0; + } if (stage == 2) return error(_("path '%s' does not have our version"), ce->name); else @@ -247,9 +255,9 @@ static int checkout_merged(int pos, const struct checkout *state) return status; } -static void mark_ce_for_checkout(struct cache_entry *ce, - char *ps_matched, - const struct checkout_opts *opts) +static void mark_ce_for_checkout_overlay(struct cache_entry *ce, + char *ps_matched, + const struct checkout_opts *opts) { ce->ce_flags &= ~CE_MATCHED; if (!opts->ignore_skipworktree && ce_skip_worktree(ce)) @@ -281,6 +289,25 @@ static void mark_ce_for_checkout(struct cache_entry *ce, ce->ce_flags |= CE_MATCHED; } +static void mark_ce_for_checkout_no_overlay(struct cache_entry *ce, + char *ps_matched, + const struct checkout_opts *opts) +{ + ce->ce_flags &= ~CE_MATCHED; + if (!opts->ignore_skipworktree && ce_skip_worktree(ce)) + return; + if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched)) { + ce->ce_flags |= CE_MATCHED; + if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) + /* + * In overlay mode, but the path is not in + * tree-ish, which means we should remove it + * from the index and the working tree. + */ + ce->ce_flags |= CE_REMOVE | CE_WT_REMOVE; + } +} + static int checkout_paths(const struct checkout_opts *opts, const char *revision) { @@ -332,7 +359,14 @@ static int checkout_paths(const struct checkout_opts *opts, * to be checked out. */ for (pos = 0; pos < active_nr; pos++) - mark_ce_for_checkout(active_cache[pos], ps_matched, opts); + if (opts->overlay_mode) + mark_ce_for_checkout_overlay(active_cache[pos], + ps_matched, + opts); + else + mark_ce_for_checkout_no_overlay(active_cache[pos], + ps_matched, + opts); if (report_path_error(ps_matched, &opts->pathspec, opts->prefix)) { free(ps_matched); @@ -353,7 +387,7 @@ static int checkout_paths(const struct checkout_opts *opts, if (opts->force) { warning(_("path '%s' is unmerged"), ce->name); } else if (opts->writeout_stage) { - errs |= check_stage(opts->writeout_stage, ce, pos); + errs |= check_stage(opts->writeout_stage, ce, pos, opts->overlay_mode); } else if (opts->merge) { errs |= check_stages((1<<2) | (1<<3), ce, pos); } else { @@ -380,12 +414,14 @@ static int checkout_paths(const struct checkout_opts *opts, continue; } if (opts->writeout_stage) - errs |= checkout_stage(opts->writeout_stage, ce, pos, &state); + errs |= checkout_stage(opts->writeout_stage, ce, pos, &state, opts->overlay_mode); else if (opts->merge) errs |= checkout_merged(pos, &state); pos = skip_same_name(ce, pos) - 1; } } + remove_marked_cache_entries(&the_index, 1); + remove_scheduled_dirs(); errs |= finish_delayed_checkout(&state); if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) @@ -547,6 +583,11 @@ static int skip_merge_working_tree(const struct checkout_opts *opts, * opts->show_progress only impacts output so doesn't require a merge */ + /* + * opts->overlay_mode cannot be used with switching branches so is + * not tested here + */ + /* * If we aren't creating a new branch any changes or updates will * happen in the existing branch. Since that could only be updating @@ -1183,6 +1224,10 @@ static int checkout_branch(struct checkout_opts *opts, die(_("'%s' cannot be used with switching branches"), "--patch"); + if (!opts->overlay_mode) + die(_("'%s' cannot be used with switching branches"), + "--no-overlay"); + if (opts->writeout_stage) die(_("'%s' cannot be used with switching branches"), "--ours/--theirs"); @@ -1271,6 +1316,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) "checkout", "control recursive updating of submodules", PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater }, OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")), + OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")), OPT_END(), }; @@ -1279,6 +1325,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.overwrite_ignore = 1; opts.prefix = prefix; opts.show_progress = -1; + opts.overlay_mode = -1; git_config(git_checkout_config, &opts); @@ -1302,6 +1349,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1) die(_("-b, -B and --orphan are mutually exclusive")); + if (opts.overlay_mode == 1 && opts.patch_mode) + die(_("-p and --overlay are mutually exclusive")); + /* * From here on, new_branch will contain the branch to be checked out, * and new_branch_force and new_orphan_branch will tell us which one of diff --git a/t/t2025-checkout-no-overlay.sh b/t/t2025-checkout-no-overlay.sh new file mode 100755 index 0000000000..76330cb5ab --- /dev/null +++ b/t/t2025-checkout-no-overlay.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +test_description='checkout --no-overlay -- ' + +. ./test-lib.sh + +test_expect_success 'setup' ' + git commit --allow-empty -m "initial" +' + +test_expect_success 'checkout --no-overlay deletes files not in ' ' + >file && + mkdir dir && + >dir/file1 && + git add file dir/file1 && + git checkout --no-overlay HEAD -- file && + test_path_is_missing file && + test_path_is_file dir/file1 +' + +test_expect_success 'checkout --no-overlay removing last file from directory' ' + git checkout --no-overlay HEAD -- dir/file1 && + test_path_is_missing dir +' + +test_expect_success 'checkout -p --overlay is disallowed' ' + test_must_fail git checkout -p --overlay HEAD 2>actual && + test_i18ngrep "fatal: -p and --overlay are mutually exclusive" actual +' + +test_expect_success '--no-overlay --theirs with D/F conflict deletes file' ' + test_commit file1 file1 && + test_commit file2 file2 && + git rm --cached file1 && + echo 1234 >file1 && + F1=$(git rev-parse HEAD:file1) && + F2=$(git rev-parse HEAD:file2) && + { + echo "100644 $F1 1 file1" && + echo "100644 $F2 2 file1" + } | git update-index --index-info && + test_path_is_file file1 && + git checkout --theirs --no-overlay -- file1 && + test_path_is_missing file1 +' + +test_done diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index d01ad8eb25..5758fffa0d 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -1436,6 +1436,7 @@ test_expect_success 'double dash "git checkout"' ' --progress Z --no-quiet Z --no-... Z + --overlay Z EOF ' From 1495ff7da526c61bff88e31fcdf419fb023a42c5 Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Tue, 8 Jan 2019 21:52:25 +0000 Subject: [PATCH 8/9] checkout: introduce checkout.overlayMode config In the previous patch we introduced a new no-overlay mode for git checkout. Some users (such as the author of this commit) may want to have this mode turned on by default as it matches their mental model more closely. Make that possible by introducing a new config option to that extend. Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- Documentation/config/checkout.txt | 7 +++++++ builtin/checkout.c | 8 +++++++- t/t2025-checkout-no-overlay.sh | 10 ++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt index c4118fa196..73380a8d86 100644 --- a/Documentation/config/checkout.txt +++ b/Documentation/config/checkout.txt @@ -21,3 +21,10 @@ checkout.optimizeNewBranch:: will not update the skip-worktree bit in the index nor add/remove files in the working directory to reflect the current sparse checkout settings nor will it show the local changes. + +checkout.overlayMode:: + In the default overlay mode, `git checkout` never + removes files from the index or the working tree. When + setting `checkout.overlayMode` to false, files that appear in + the index and working tree, but not in are removed, + to make them match exactly. diff --git a/builtin/checkout.c b/builtin/checkout.c index 0c5fe948ef..b5dfc45736 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1019,13 +1019,19 @@ static int switch_branches(const struct checkout_opts *opts, static int git_checkout_config(const char *var, const char *value, void *cb) { + struct checkout_opts *opts = cb; + if (!strcmp(var, "checkout.optimizenewbranch")) { checkout_optimize_new_branch = git_config_bool(var, value); return 0; } + if (!strcmp(var, "checkout.overlaymode")) { + opts->overlay_mode = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "diff.ignoresubmodules")) { - struct checkout_opts *opts = cb; handle_ignore_submodules_arg(&opts->diff_options, value); return 0; } diff --git a/t/t2025-checkout-no-overlay.sh b/t/t2025-checkout-no-overlay.sh index 76330cb5ab..a4912e35cb 100755 --- a/t/t2025-checkout-no-overlay.sh +++ b/t/t2025-checkout-no-overlay.sh @@ -44,4 +44,14 @@ test_expect_success '--no-overlay --theirs with D/F conflict deletes file' ' test_path_is_missing file1 ' +test_expect_success 'checkout with checkout.overlayMode=false deletes files not in ' ' + >file && + mkdir dir && + >dir/file1 && + git add file dir/file1 && + git -c checkout.overlayMode=false checkout HEAD -- file && + test_path_is_missing file && + test_path_is_file dir/file1 +' + test_done From e92aa0e4ef5a91781530449f9466a45c16c91f7f Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Mon, 4 Feb 2019 21:13:16 +0000 Subject: [PATCH 9/9] revert "checkout: introduce checkout.overlayMode config" This reverts 1495ff7da5 ("checkout: introduce checkout.overlayMode config", 2019-01-08) and thus removes the checkout.overlayMode config option. The option was originally introduced to give users the option to make the new no-overlay behaviour the default. However users may be using 'git checkout' in scripts, even though it is porcelain. Users setting the option to false may actually end up accidentally breaking scripts. With the introduction of a new subcommand that will make the behaviour the default, the config option will not be needed anymore anyway. Revert the commit and remove the config option, so we don't risk breaking scripts. Suggested-by: Jonathan Nieder Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- Documentation/config/checkout.txt | 7 ------- builtin/checkout.c | 8 +------- t/t2025-checkout-no-overlay.sh | 10 ---------- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt index 73380a8d86..c4118fa196 100644 --- a/Documentation/config/checkout.txt +++ b/Documentation/config/checkout.txt @@ -21,10 +21,3 @@ checkout.optimizeNewBranch:: will not update the skip-worktree bit in the index nor add/remove files in the working directory to reflect the current sparse checkout settings nor will it show the local changes. - -checkout.overlayMode:: - In the default overlay mode, `git checkout` never - removes files from the index or the working tree. When - setting `checkout.overlayMode` to false, files that appear in - the index and working tree, but not in are removed, - to make them match exactly. diff --git a/builtin/checkout.c b/builtin/checkout.c index b5dfc45736..0c5fe948ef 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1019,19 +1019,13 @@ static int switch_branches(const struct checkout_opts *opts, static int git_checkout_config(const char *var, const char *value, void *cb) { - struct checkout_opts *opts = cb; - if (!strcmp(var, "checkout.optimizenewbranch")) { checkout_optimize_new_branch = git_config_bool(var, value); return 0; } - if (!strcmp(var, "checkout.overlaymode")) { - opts->overlay_mode = git_config_bool(var, value); - return 0; - } - if (!strcmp(var, "diff.ignoresubmodules")) { + struct checkout_opts *opts = cb; handle_ignore_submodules_arg(&opts->diff_options, value); return 0; } diff --git a/t/t2025-checkout-no-overlay.sh b/t/t2025-checkout-no-overlay.sh index a4912e35cb..76330cb5ab 100755 --- a/t/t2025-checkout-no-overlay.sh +++ b/t/t2025-checkout-no-overlay.sh @@ -44,14 +44,4 @@ test_expect_success '--no-overlay --theirs with D/F conflict deletes file' ' test_path_is_missing file1 ' -test_expect_success 'checkout with checkout.overlayMode=false deletes files not in ' ' - >file && - mkdir dir && - >dir/file1 && - git add file dir/file1 && - git -c checkout.overlayMode=false checkout HEAD -- file && - test_path_is_missing file && - test_path_is_file dir/file1 -' - test_done