From: Ben Peart <Ben.Peart@xxxxxxxxxxxxx> Skip merging the commit, updating the index and working directory if and only if we are creating a new branch via "git checkout -b <new_branch>." Any other checkout options will still go through the former code path. If sparse_checkout is on, require the user to manually opt in to this optimzed behavior by setting the config setting checkout.optimizeNewBranch to true as we will no longer update the skip-worktree bit in the index, nor add/remove files in the working directory to reflect the current sparse checkout settings. Signed-off-by: Ben Peart <Ben.Peart@xxxxxxxxxxxxx> --- The biggest change in this version was suggested in feedback to the last patch. I have turned on the optimzation by default if sparse-checkout is not on so that most users do not have to set anything and they will get the benefit of the optimization. Because users that use sparse checkout are probably doing so because they have a large working directory, they stand to benefit the most from this optimization. To enable them to benefit, I added a "checkout.optimizeNewBranch" config setting that allows them to opt-in to this optimization if they are willing to accept the change in behavior. I updated the documentation which should make it clear exactly what the change in behavior is. Notes: Base Ref: master Web-Diff: https://github.com/benpeart/git/commit/5ea167fe90 Checkout: git fetch https://github.com/benpeart/git checkout-b-v2 && git checkout 5ea167fe90 ### Patches Documentation/config.txt | 5 ++ builtin/checkout.c | 113 +++++++++++++++++++++++++++++-- t/t1090-sparse-checkout-scope.sh | 14 ++++ 3 files changed, 128 insertions(+), 4 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 43b2de7b5f..acf81143d4 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1101,6 +1101,11 @@ browser.<tool>.path:: browse HTML help (see `-w` option in linkgit:git-help[1]) or a working repository in gitweb (see linkgit:git-instaweb[1]). +checkout.optimizeNewBranch + When set to true, "git checkout -b <new_branch>" 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. + clean.requireForce:: A boolean to make git-clean do nothing unless given -f, -i or -n. Defaults to true. diff --git a/builtin/checkout.c b/builtin/checkout.c index 28627650cd..991b71a341 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -24,6 +24,8 @@ #include "submodule-config.h" #include "submodule.h" +static int checkout_optimize_new_branch; + static const char * const checkout_usage[] = { N_("git checkout [<options>] <branch>"), N_("git checkout [<options>] [<branch>] -- <file>..."), @@ -41,6 +43,10 @@ struct checkout_opts { int ignore_skipworktree; int ignore_other_worktrees; int show_progress; + /* + * If new checkout options are added, skip_merge_working_tree + * should be updated accordingly. + */ const char *new_branch; const char *new_branch_force; @@ -471,6 +477,98 @@ static void setup_branch_path(struct branch_info *branch) branch->path = strbuf_detach(&buf, NULL); } +/* + * Skip merging the trees, updating the index and working directory if and + * only if we are creating a new branch via "git checkout -b <new_branch>." + */ +static int skip_merge_working_tree(const struct checkout_opts *opts, + const struct branch_info *old_branch_info, + const struct branch_info *new_branch_info) +{ + /* + * Do the merge if sparse checkout is on and the user has not opted in + * to the optimized behavior + */ + if (core_apply_sparse_checkout && !checkout_optimize_new_branch) + return 0; + + /* + * We must do the merge if we are actually moving to a new commit. + */ + if (!old_branch_info->commit || !new_branch_info->commit || + oidcmp(&old_branch_info->commit->object.oid, &new_branch_info->commit->object.oid)) + return 0; + + /* + * opts->patch_mode cannot be used with switching branches so is + * not tested here + */ + + /* + * opts->quiet only impacts output so doesn't require a merge + */ + + /* + * Honor the explicit request for a three-way merge or to throw away + * local changes + */ + if (opts->merge || opts->force) + return 0; + + /* + * --detach is documented as "updating the index and the files in the + * working tree" but this optimization skips those steps so fall through + * to the regular code path. + */ + if (opts->force_detach) + return 0; + + /* + * opts->writeout_stage cannot be used with switching branches so is + * not tested here + */ + + /* + * Honor the explicit ignore requests + */ + if (!opts->overwrite_ignore || opts->ignore_skipworktree || + opts->ignore_other_worktrees) + return 0; + + /* + * opts->show_progress only impacts output so doesn't require a merge + */ + + /* + * If we aren't creating a new branch any changes or updates will + * happen in the existing branch. Since that could only be updating + * the index and working directory, we don't want to skip those steps + * or we've defeated any purpose in running the command. + */ + if (!opts->new_branch) + return 0; + + /* + * new_branch_force is defined to "create/reset and checkout a branch" + * so needs to go through the merge to do the reset + */ + if (opts->new_branch_force) + return 0; + + /* + * A new orphaned branch requrires the index and the working tree to be + * adjusted to <start_point> + */ + if (opts->new_orphan_branch) + return 0; + + /* + * Remaining variables are not checkout options but used to track state + */ + + return 1; +} + static int merge_working_tree(const struct checkout_opts *opts, struct branch_info *old_branch_info, struct branch_info *new_branch_info, @@ -845,10 +943,12 @@ static int switch_branches(const struct checkout_opts *opts, parse_commit_or_die(new_branch_info->commit); } - ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error); - if (ret) { - free(path_to_free); - return ret; + if (!skip_merge_working_tree(opts, &old_branch_info, new_branch_info)) { + ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error); + if (ret) { + free(path_to_free); + return ret; + } } if (!opts->quiet && !old_branch_info.path && old_branch_info.commit && new_branch_info->commit != old_branch_info.commit) @@ -863,6 +963,11 @@ static int switch_branches(const struct checkout_opts *opts, static int git_checkout_config(const char *var, const char *value, void *cb) { + if (!strcmp(var, "checkout.optimizenewbranch")) { + checkout_optimize_new_branch = 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); diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh index 1f61eb3e88..25d7c700f6 100755 --- a/t/t1090-sparse-checkout-scope.sh +++ b/t/t1090-sparse-checkout-scope.sh @@ -31,6 +31,20 @@ test_expect_success 'perform sparse checkout of master' ' test_path_is_file c ' +test_expect_success 'checkout -b checkout.optimizeNewBranch interaction' ' + cp .git/info/sparse-checkout .git/info/sparse-checkout.bak && + test_when_finished " + mv -f .git/info/sparse-checkout.bak .git/info/sparse-checkout + git checkout master + " && + echo "/b" >>.git/info/sparse-checkout && + test "$(git ls-files -t b)" = "S b" && + git -c checkout.optimizeNewBranch=true checkout -b fast && + test "$(git ls-files -t b)" = "S b" && + git checkout -b slow && + test "$(git ls-files -t b)" = "H b" +' + test_expect_success 'merge feature branch into sparse checkout of master' ' git merge feature && test_path_is_file a && base-commit: ffc6fa0e396238de3a30623912980263b4f283ab -- 2.17.0.gvfs.1.123.g449c066