mirror of
https://github.com/git/git
synced 2024-08-27 11:39:22 +00:00
checkout: optimize "git checkout -b <new_branch>"
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. For comparison, running "git checkout -b <new_branch>" on a large repo takes: 14.6 seconds - without this patch 0.3 seconds - with this patch Signed-off-by: Ben Peart <Ben.Peart@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
ffc6fa0e39
commit
fa655d8411
|
@ -1101,6 +1101,14 @@ 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
|
||||
Optimizes the performance of "git checkout -b <new_branch>" when
|
||||
using sparse-checkout. When set to true, git will not update the
|
||||
repo based on the current sparse-checkout settings. This means it
|
||||
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.
|
||||
|
||||
clean.requireForce::
|
||||
A boolean to make git-clean do nothing unless given -f,
|
||||
-i or -n. Defaults to true.
|
||||
|
|
|
@ -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,19 @@ 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;
|
||||
/* optimize the "checkout -b <new_branch> path */
|
||||
if (skip_merge_working_tree(opts, &old_branch_info, new_branch_info)) {
|
||||
if (!checkout_optimize_new_branch && !opts->quiet) {
|
||||
if (read_cache_preload(NULL) < 0)
|
||||
return error(_("index file corrupt"));
|
||||
show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
|
||||
}
|
||||
} else {
|
||||
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 +970,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);
|
||||
|
|
|
@ -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 &&
|
||||
|
|
Loading…
Reference in a new issue