Skip to content

Commit 75be0fd

Browse files
committed
Revert "switch: no worktree status unless real branch switch happens"
This reverts commit 65f099b, which removed logic for avoiding extra cost in "git checkout -b" in favor of the new "git switch -c". The new switch builtin is still an experimental feature, so stating "large repos should use 'git switch'" is too aggressive. When a user runs "git checkout -b <branchname>" they are pretty clear in stating that they only want to create a branch and not update the working directory. The difficulty comes in detecting that we are actually in that case. That is why the skip_merge_working_tree() method is so detailed: there are many settings to check to ensure the arguments match as expected. Signed-off-by: Derrick Stolee <[email protected]>
1 parent 5fa0f52 commit 75be0fd

File tree

3 files changed

+148
-7
lines changed

3 files changed

+148
-7
lines changed

Documentation/config/checkout.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,11 @@ will checkout the '<something>' branch on another remote,
1616
and by linkgit:git-worktree[1] when 'git worktree add' refers to a
1717
remote branch. This setting might be used for other checkout-like
1818
commands or functionality in the future.
19+
20+
checkout.optimizeNewBranch::
21+
Optimizes the performance of "git checkout -b <new_branch>" when
22+
using sparse-checkout. When set to true, git will not update the
23+
repo based on the current sparse-checkout settings. This means it
24+
will not update the skip-worktree bit in the index nor add/remove
25+
files in the working directory to reflect the current sparse checkout
26+
settings nor will it show the local changes.

builtin/checkout.c

Lines changed: 126 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
#include "wt-status.h"
2828
#include "xdiff-interface.h"
2929

30+
static int checkout_optimize_new_branch;
31+
3032
static const char * const checkout_usage[] = {
3133
N_("git checkout [<options>] <branch>"),
3234
N_("git checkout [<options>] [<branch>] -- <file>..."),
@@ -71,6 +73,11 @@ struct checkout_opts {
7173
const char *ignore_unmerged_opt;
7274
int ignore_unmerged;
7375

76+
/*
77+
* If new checkout options are added, skip_merge_working_tree
78+
* should be updated accordingly.
79+
*/
80+
7481
const char *new_branch;
7582
const char *new_branch_force;
7683
const char *new_orphan_branch;
@@ -637,6 +644,112 @@ static void setup_branch_path(struct branch_info *branch)
637644
branch->path = strbuf_detach(&buf, NULL);
638645
}
639646

647+
/*
648+
* Skip merging the trees, updating the index and working directory if and
649+
* only if we are creating a new branch via "git checkout -b <new_branch>."
650+
*/
651+
static int skip_merge_working_tree(const struct checkout_opts *opts,
652+
const struct branch_info *old_branch_info,
653+
const struct branch_info *new_branch_info)
654+
{
655+
/*
656+
* Do the merge if sparse checkout is on and the user has not opted in
657+
* to the optimized behavior
658+
*/
659+
if (core_apply_sparse_checkout && !checkout_optimize_new_branch)
660+
return 0;
661+
662+
/*
663+
* We must do the merge if we are actually moving to a new commit.
664+
*/
665+
if (!old_branch_info->commit || !new_branch_info->commit ||
666+
!oideq(&old_branch_info->commit->object.oid,
667+
&new_branch_info->commit->object.oid))
668+
return 0;
669+
670+
/*
671+
* opts->patch_mode cannot be used with switching branches so is
672+
* not tested here
673+
*/
674+
675+
/*
676+
* opts->quiet only impacts output so doesn't require a merge
677+
*/
678+
679+
/*
680+
* Honor the explicit request for a three-way merge or to throw away
681+
* local changes
682+
*/
683+
if (opts->merge || opts->force)
684+
return 0;
685+
686+
/*
687+
* --detach is documented as "updating the index and the files in the
688+
* working tree" but this optimization skips those steps so fall through
689+
* to the regular code path.
690+
*/
691+
if (opts->force_detach)
692+
return 0;
693+
694+
/*
695+
* opts->writeout_stage cannot be used with switching branches so is
696+
* not tested here
697+
*/
698+
699+
/*
700+
* Honor the explicit ignore requests
701+
*/
702+
if (!opts->overwrite_ignore || opts->ignore_skipworktree ||
703+
opts->ignore_other_worktrees)
704+
return 0;
705+
706+
/*
707+
* opts->show_progress only impacts output so doesn't require a merge
708+
*/
709+
710+
/*
711+
* opts->overlay_mode cannot be used with switching branches so is
712+
* not tested here
713+
*/
714+
715+
/*
716+
* If we aren't creating a new branch any changes or updates will
717+
* happen in the existing branch. Since that could only be updating
718+
* the index and working directory, we don't want to skip those steps
719+
* or we've defeated any purpose in running the command.
720+
*/
721+
if (!opts->new_branch)
722+
return 0;
723+
724+
/*
725+
* new_branch_force is defined to "create/reset and checkout a branch"
726+
* so needs to go through the merge to do the reset
727+
*/
728+
if (opts->new_branch_force)
729+
return 0;
730+
731+
/*
732+
* A new orphaned branch requrires the index and the working tree to be
733+
* adjusted to <start_point>
734+
*/
735+
if (opts->new_orphan_branch)
736+
return 0;
737+
738+
/*
739+
* Remaining variables are not checkout options but used to track state
740+
*/
741+
742+
/*
743+
* Do the merge if this is the initial checkout. We cannot use
744+
* is_cache_unborn() here because the index hasn't been loaded yet
745+
* so cache_nr and timestamp.sec are always zero.
746+
*/
747+
if (!file_exists(get_index_file()))
748+
return 0;
749+
750+
return 1;
751+
}
752+
640753
static int merge_working_tree(const struct checkout_opts *opts,
641754
struct branch_info *old_branch_info,
642755
struct branch_info *new_branch_info,
@@ -1020,7 +1133,6 @@ static int switch_branches(const struct checkout_opts *opts,
10201133
void *path_to_free;
10211134
struct object_id rev;
10221135
int flag, writeout_error = 0;
1023-
int do_merge = 1;
10241136

10251137
trace2_cmd_mode("branch");
10261138

@@ -1039,7 +1151,6 @@ static int switch_branches(const struct checkout_opts *opts,
10391151
BUG("'switch --orphan' should never accept a commit as starting point");
10401152
new_branch_info->commit = NULL;
10411153
new_branch_info->name = "(empty)";
1042-
do_merge = 1;
10431154
}
10441155

10451156
if (!new_branch_info->name) {
@@ -1048,12 +1159,16 @@ static int switch_branches(const struct checkout_opts *opts,
10481159
if (!new_branch_info->commit)
10491160
die(_("You are on a branch yet to be born"));
10501161
parse_commit_or_die(new_branch_info->commit);
1051-
1052-
if (opts->only_merge_on_switching_branches)
1053-
do_merge = 0;
10541162
}
10551163

1056-
if (do_merge) {
1164+
/* optimize the "checkout -b <new_branch> path */
1165+
if (skip_merge_working_tree(opts, &old_branch_info, new_branch_info)) {
1166+
if (!checkout_optimize_new_branch && !opts->quiet) {
1167+
if (read_cache_preload(NULL) < 0)
1168+
return error(_("index file corrupt"));
1169+
show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
1170+
}
1171+
} else {
10571172
ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
10581173
if (ret) {
10591174
free(path_to_free);
@@ -1073,6 +1188,11 @@ static int switch_branches(const struct checkout_opts *opts,
10731188

10741189
static int git_checkout_config(const char *var, const char *value, void *cb)
10751190
{
1191+
if (!strcmp(var, "checkout.optimizenewbranch")) {
1192+
checkout_optimize_new_branch = git_config_bool(var, value);
1193+
return 0;
1194+
}
1195+
10761196
if (!strcmp(var, "diff.ignoresubmodules")) {
10771197
struct checkout_opts *opts = cb;
10781198
handle_ignore_submodules_arg(&opts->diff_options, value);
@@ -1747,7 +1867,6 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
17471867
opts.accept_ref = 1;
17481868
opts.accept_pathspec = 0;
17491869
opts.switch_branch_doing_nothing_is_ok = 0;
1750-
opts.only_merge_on_switching_branches = 1;
17511870
opts.implicit_detach = 0;
17521871
opts.can_switch_when_in_progress = 0;
17531872
opts.orphan_from_empty_tree = 1;

t/t1090-sparse-checkout-scope.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ test_expect_success 'perform sparse checkout of master' '
3131
test_path_is_file c
3232
'
3333

34+
test_expect_success 'checkout -b checkout.optimizeNewBranch interaction' '
35+
cp .git/info/sparse-checkout .git/info/sparse-checkout.bak &&
36+
test_when_finished "
37+
mv -f .git/info/sparse-checkout.bak .git/info/sparse-checkout
38+
git checkout master
39+
" &&
40+
echo "/b" >>.git/info/sparse-checkout &&
41+
test "$(git ls-files -t b)" = "S b" &&
42+
git -c checkout.optimizeNewBranch=true checkout -b fast &&
43+
test "$(git ls-files -t b)" = "S b" &&
44+
git checkout -b slow &&
45+
test "$(git ls-files -t b)" = "H b"
46+
'
47+
3448
test_expect_success 'merge feature branch into sparse checkout of master' '
3549
git merge feature &&
3650
test_path_is_file a &&

0 commit comments

Comments
 (0)