Teach git to avoid unnecessary merge during trivial checkout. When running 'git checkout -b foo' git follows a common code path through the expensive merge_working_tree even when it is unnecessary. As a result, 95% of the time is spent in merge_working_tree doing the 2-way merge between the new and old commit trees that is unneeded. The time breakdown is as follows: merge_working_tree <-- 95% unpack_trees <-- 80% traverse_trees <-- 50% cache_tree_update <-- 17% mark_new_skip_worktree <-- 10% With a large repo, this cost is pronounced. Using "git checkout -b r" to create and switch to a new branch costs 166 seconds (all times worst case with a cold file system cache). git.c:406 trace: built-in: git 'checkout' '-b' 'r' read-cache.c:1667 performance: 17.442926555 s: read_index_from name-hash.c:128 performance: 2.912145231 s: lazy_init_name_hash read-cache.c:2208 performance: 4.387713335 s: write_locked_index trace.c:420 performance: 166.458921289 s: git command: 'c:\Users\benpeart\bin\git.exe' 'checkout' '-b' 'r' Switched to a new branch 'r' By adding a test to skip the unnecessary call to merge_working_tree in this case reduces the cost to 16 seconds. git.c:406 trace: built-in: git 'checkout' '-b' 's' read-cache.c:1667 performance: 16.100742476 s: read_index_from trace.c:420 performance: 16.461547867 s: git command: 'c:\Users\benpeart\bin\git.exe' 'checkout' '-b' 's' Switched to a new branch 's' Signed-off-by: Ben Peart <benpeart@xxxxxxxxxxxxx> --- builtin/checkout.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/builtin/checkout.c b/builtin/checkout.c index 8672d07..595d64b 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -827,10 +827,25 @@ static int switch_branches(const struct checkout_opts *opts, parse_commit_or_die(new->commit); } - ret = merge_working_tree(opts, &old, new, &writeout_error); - if (ret) { - free(path_to_free); - return ret; + /* + * Optimize the performance of checkout when the current and + * new branch have the same OID and avoid the trivial merge. + * For example, a "git checkout -b foo" just needs to create + * the new ref and report the stats. + */ + if (!old.commit || !new->commit + || oidcmp(&old.commit->object.oid, &new->commit->object.oid) + || !opts->new_branch || opts->new_branch_force || opts->new_orphan_branch + || opts->patch_mode || opts->merge || opts->force || opts->force_detach + || opts->writeout_stage || !opts->overwrite_ignore + || opts->ignore_skipworktree || opts->ignore_other_worktrees + || opts->new_branch_log || opts->branch_exists || opts->prefix + || opts->source_tree) { + ret = merge_working_tree(opts, &old, new, &writeout_error); + if (ret) { + free(path_to_free); + return ret; + } } if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) -- 2.10.0.windows.1