From: Ben Peart <Ben.Peart@microsoft.com>
To: Ben Peart <Ben.Peart@microsoft.com>
Cc: "git@vger.kernel.org" <git@vger.kernel.org>,
"gitster@pobox.com" <gitster@pobox.com>,
"pclouds@gmail.com" <pclouds@gmail.com>,
"sunshine@sunshineco.com" <sunshine@sunshineco.com>,
Ben Peart <Ben.Peart@microsoft.com>
Subject: [PATCH v3] checkout: optimize "git checkout -b <new_branch>"
Date: Thu, 16 Aug 2018 18:27:11 +0000 [thread overview]
Message-ID: <20180816182653.15580-1-benpeart@microsoft.com> (raw)
In-Reply-To: <20180724180122.29212-1-benpeart@microsoft.com>
From: Ben Peart <Ben.Peart@microsoft.com>
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>
---
The biggest change in this version is that I have added the logic to call
show_local_changes() on the optimized path. Since this can be expensive on
large repos (see below) this behavior is turned off if the user sets the
"checkout.optimizeNewBranch" setting.
$ git checkout -b newbranch
read-cache.c:2006 performance: 0.517875500 s: read cache .git/index
name-hash.c:605 performance: 0.277765000 s: initialize name hash
preload-index.c:111 performance: 0.019401300 s: preload index
diff-lib.c:527 performance: 3.807563700 s: diff-index
Switched to a new branch 'newbranch'
trace.c:420 performance: 5.044219600 s: git command: c:git checkout -b newbranch
$ git checkout -b newbranch1
Switched to a new branch 'newbranch1'
trace.c:420 performance: 0.332873600 s: git command: c:git checkout -b newbranch111
Notes:
Base Ref: master
Web-Diff: https://github.com/benpeart/git/commit/aebe02c966
Checkout: git fetch https://github.com/benpeart/git checkout-b-v3 && git checkout aebe02c966
### Patches
Documentation/config.txt | 8 +++
builtin/checkout.c | 120 +++++++++++++++++++++++++++++--
t/t1090-sparse-checkout-scope.sh | 14 ++++
3 files changed, 138 insertions(+), 4 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index fd8d27e761..2298ecd753 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1135,6 +1135,14 @@ and by linkgit:git-worktree[1] when 'git worktree add' refers to a
remote branch. This setting might be used for other checkout-like
commands or functionality in the future.
+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.
diff --git a/builtin/checkout.c b/builtin/checkout.c
index cb6bb76312..26b1a5053a 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -25,6 +25,8 @@
#include "submodule.h"
#include "advice.h"
+static int checkout_optimize_new_branch;
+
static const char * const checkout_usage[] = {
N_("git checkout [<options>] <branch>"),
N_("git checkout [<options>] [<branch>] -- <file>..."),
@@ -42,6 +44,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;
@@ -472,6 +478,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,
@@ -846,10 +944,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)
@@ -864,6 +971,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: 63749b2dea5d1501ff85bab7b8a7f64911d21dea
--
2.18.0.windows.1
next prev parent reply other threads:[~2018-08-16 18:27 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-07-24 18:01 [PATCH v1] checkout: optionally speed up "git checkout -b foo" Ben Peart
2018-07-24 18:42 ` Eric Sunshine
2018-07-24 19:45 ` Ben Peart
2018-07-26 15:04 ` Junio C Hamano
2018-07-26 18:59 ` Eric Sunshine
2018-07-26 19:08 ` Eric Sunshine
2018-07-24 19:21 ` Junio C Hamano
2018-07-24 20:47 ` Ben Peart
2018-07-31 16:39 ` [PATCH v2] checkout: optimize "git checkout -b <new_branch>" Ben Peart
2018-07-31 20:01 ` Junio C Hamano
2018-08-01 15:10 ` Duy Nguyen
2018-08-02 18:02 ` Ben Peart
2018-08-03 15:58 ` Duy Nguyen
2018-08-06 14:25 ` Ben Peart
2018-08-15 21:05 ` Ben Peart
2018-08-05 8:57 ` Duy Nguyen
2018-08-16 18:27 ` Ben Peart [this message]
2018-08-16 18:37 ` [PATCH v3] " Duy Nguyen
2018-08-17 12:37 ` Ben Peart
2018-08-19 1:44 ` Elijah Newren
2018-08-20 13:40 ` Ben Peart
2018-08-20 18:16 ` Elijah Newren
2018-08-21 14:51 ` Duy Nguyen
2018-08-30 17:22 ` Elijah Newren
2018-09-04 16:46 ` Duy Nguyen
2018-08-20 18:31 ` Junio C Hamano
2018-09-18 5:34 ` [PATCH] config doc: add missing list separator for checkout.optimizeNewBranch Ævar Arnfjörð Bjarmason
2018-09-18 16:57 ` Taylor Blau
2018-09-18 17:16 ` Jeff King
2018-09-18 17:20 ` Taylor Blau
2018-09-18 17:13 ` Jeff King
2018-09-19 4:41 ` Ævar Arnfjörð Bjarmason
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: http://vger.kernel.org/majordomo-info.html
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20180816182653.15580-1-benpeart@microsoft.com \
--to=ben.peart@microsoft.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=pclouds@gmail.com \
--cc=sunshine@sunshineco.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://80x24.org/mirrors/git.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).