Skip to content

Commit c819d38

Browse files
Ben PeartBen Peart
authored andcommitted
PR 141948: Restructure fast checkout path to be closer to what we're trying to push upstream
- Enable fast path when not in git root. - Enable calling show_local_changes at end of checkout when on fast path.
1 parent 7cd9f06 commit c819d38

File tree

1 file changed

+205
-125
lines changed

1 file changed

+205
-125
lines changed

builtin/checkout.c

Lines changed: 205 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ struct checkout_opts {
3939
int ignore_skipworktree;
4040
int ignore_other_worktrees;
4141
int show_progress;
42+
/*
43+
* If new checkout options are added, needs_working_tree_merge
44+
* should be updated accordingly.
45+
*/
4246

4347
const char *new_branch;
4448
const char *new_branch_force;
@@ -461,130 +465,222 @@ static void setup_branch_path(struct branch_info *branch)
461465
branch->path = strbuf_detach(&buf, NULL);
462466
}
463467

464-
static int merge_working_tree(const struct checkout_opts *opts,
465-
struct branch_info *old,
466-
struct branch_info *new,
467-
int *writeout_error)
468+
static int needs_working_tree_merge(const struct checkout_opts *opts,
469+
const struct branch_info *old,
470+
const struct branch_info *new)
468471
{
469-
int ret;
470-
struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
472+
/*
473+
* We must do the merge if we are actually moving to a new
474+
* commit tree.
475+
*/
476+
if (!old->commit || !new->commit ||
477+
oidcmp(&old->commit->tree->object.oid, &new->commit->tree->object.oid))
478+
return 1;
471479

472-
hold_locked_index(lock_file, 1);
473-
if (read_cache_preload(NULL) < 0)
474-
return error(_("corrupt index file"));
480+
/*
481+
* opts->patch_mode cannot be used with switching branches so is
482+
* not tested here
483+
*/
475484

476-
resolve_undo_clear();
477-
if (opts->force) {
478-
ret = reset_tree(new->commit->tree, opts, 1, writeout_error);
479-
if (ret)
480-
return ret;
481-
} else {
482-
struct tree_desc trees[2];
483-
struct tree *tree;
484-
struct unpack_trees_options topts;
485+
/*
486+
* opts->quiet only impacts output so doesn't require a merge
487+
*/
485488

486-
memset(&topts, 0, sizeof(topts));
487-
topts.head_idx = -1;
488-
topts.src_index = &the_index;
489-
topts.dst_index = &the_index;
489+
/*
490+
* Honor the explicit request for a three-way merge or to throw away
491+
* local changes
492+
*/
493+
if (opts->merge || opts->force)
494+
return 1;
490495

491-
setup_unpack_trees_porcelain(&topts, "checkout");
496+
/*
497+
* --detach is documented as "updating the index and the files in the
498+
* working tree" but this optimization skips those steps so fall through
499+
* to the regular code path.
500+
*/
501+
if (opts->force_detach)
502+
return 1;
492503

493-
refresh_cache(REFRESH_QUIET);
504+
/*
505+
* opts->writeout_stage cannot be used with switching branches so is
506+
* not tested here
507+
*/
494508

495-
if (unmerged_cache()) {
496-
error(_("you need to resolve your current index first"));
497-
return 1;
498-
}
509+
/*
510+
* Honor the explicit ignore requests
511+
*/
512+
if (!opts->overwrite_ignore || opts->ignore_skipworktree ||
513+
opts->ignore_other_worktrees)
514+
return 1;
499515

500-
/* 2-way merge to the new branch */
501-
topts.initial_checkout = is_cache_unborn();
502-
topts.update = 1;
503-
topts.merge = 1;
504-
topts.gently = opts->merge && old->commit;
505-
topts.verbose_update = opts->show_progress;
506-
topts.fn = twoway_merge;
507-
if (opts->overwrite_ignore) {
508-
topts.dir = xcalloc(1, sizeof(*topts.dir));
509-
topts.dir->flags |= DIR_SHOW_IGNORED;
510-
setup_standard_excludes(topts.dir);
511-
}
512-
tree = parse_tree_indirect(old->commit ?
513-
old->commit->object.oid.hash :
514-
EMPTY_TREE_SHA1_BIN);
515-
init_tree_desc(&trees[0], tree->buffer, tree->size);
516-
tree = parse_tree_indirect(new->commit->object.oid.hash);
517-
init_tree_desc(&trees[1], tree->buffer, tree->size);
518-
519-
ret = unpack_trees(2, trees, &topts);
520-
if (ret == -1) {
521-
/*
522-
* Unpack couldn't do a trivial merge; either
523-
* give up or do a real merge, depending on
524-
* whether the merge flag was used.
525-
*/
526-
struct tree *result;
527-
struct tree *work;
528-
struct merge_options o;
529-
if (!opts->merge)
530-
return 1;
516+
/*
517+
* opts->show_progress only impacts output so doesn't require a merge
518+
*/
531519

532-
/*
533-
* Without old->commit, the below is the same as
534-
* the two-tree unpack we already tried and failed.
535-
*/
536-
if (!old->commit)
537-
return 1;
520+
/*
521+
* If we aren't creating a new branch any changes or updates will
522+
* happen in the existing branch. Since that could only be updating
523+
* the index and working directory, we don't want to skip those steps
524+
* or we've defeated any purpose in running the command.
525+
*/
526+
if (!opts->new_branch)
527+
return 1;
528+
529+
/*
530+
* new_branch_force is defined to "create/reset and checkout a branch"
531+
* so needs to go through the merge to do the reset
532+
*/
533+
if (opts->new_branch_force)
534+
return 1;
538535

539-
/* Do more real merge */
536+
/*
537+
* A new orphaned branch requrires the index and the working tree to be
538+
* adjusted to <start_point>
539+
*/
540+
if (opts->new_orphan_branch)
541+
return 1;
540542

541-
/*
542-
* We update the index fully, then write the
543-
* tree from the index, then merge the new
544-
* branch with the current tree, with the old
545-
* branch as the base. Then we reset the index
546-
* (but not the working tree) to the new
547-
* branch, leaving the working tree as the
548-
* merged version, but skipping unmerged
549-
* entries in the index.
550-
*/
543+
/*
544+
* Remaining variables are not checkout options but used to track state
545+
*/
551546

552-
add_files_to_cache(NULL, NULL, 0, 0);
553-
/*
554-
* NEEDSWORK: carrying over local changes
555-
* when branches have different end-of-line
556-
* normalization (or clean+smudge rules) is
557-
* a pain; plumb in an option to set
558-
* o.renormalize?
559-
*/
560-
init_merge_options(&o);
561-
o.verbosity = 0;
562-
work = write_tree_from_memory(&o);
547+
return 0;
548+
}
563549

564-
ret = reset_tree(new->commit->tree, opts, 1,
565-
writeout_error);
566-
if (ret)
567-
return ret;
568-
o.ancestor = old->name;
569-
o.branch1 = new->name;
570-
o.branch2 = "local";
571-
merge_trees(&o, new->commit->tree, work,
572-
old->commit->tree, &result);
573-
ret = reset_tree(new->commit->tree, opts, 0,
574-
writeout_error);
550+
551+
static int merge_working_tree(const struct checkout_opts *opts,
552+
struct branch_info *old,
553+
struct branch_info *new,
554+
int *writeout_error)
555+
{
556+
/*
557+
* Skip merging the trees, updating the index, and work tree only if we
558+
* are simply creating a new branch via "git checkout -b foo." Any other
559+
* options or usage will continue to do all these steps.
560+
*/
561+
if (!gvfs_config_is_set(GVFS_SKIP_MERGE_IN_CHECKOUT) ||
562+
needs_working_tree_merge(opts, old, new)) {
563+
564+
int ret;
565+
struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
566+
567+
hold_locked_index(lock_file, 1);
568+
if (read_cache_preload(NULL) < 0)
569+
return error(_("corrupt index file"));
570+
571+
resolve_undo_clear();
572+
if (opts->force) {
573+
ret = reset_tree(new->commit->tree, opts, 1, writeout_error);
575574
if (ret)
576575
return ret;
576+
} else {
577+
struct tree_desc trees[2];
578+
struct tree *tree;
579+
struct unpack_trees_options topts;
580+
581+
memset(&topts, 0, sizeof(topts));
582+
topts.head_idx = -1;
583+
topts.src_index = &the_index;
584+
topts.dst_index = &the_index;
585+
586+
setup_unpack_trees_porcelain(&topts, "checkout");
587+
588+
refresh_cache(REFRESH_QUIET);
589+
590+
if (unmerged_cache()) {
591+
error(_("you need to resolve your current index first"));
592+
return 1;
593+
}
594+
595+
/* 2-way merge to the new branch */
596+
topts.initial_checkout = is_cache_unborn();
597+
topts.update = 1;
598+
topts.merge = 1;
599+
topts.gently = opts->merge && old->commit;
600+
topts.verbose_update = opts->show_progress;
601+
topts.fn = twoway_merge;
602+
if (opts->overwrite_ignore) {
603+
topts.dir = xcalloc(1, sizeof(*topts.dir));
604+
topts.dir->flags |= DIR_SHOW_IGNORED;
605+
setup_standard_excludes(topts.dir);
606+
}
607+
tree = parse_tree_indirect(old->commit ?
608+
old->commit->object.oid.hash :
609+
EMPTY_TREE_SHA1_BIN);
610+
init_tree_desc(&trees[0], tree->buffer, tree->size);
611+
tree = parse_tree_indirect(new->commit->object.oid.hash);
612+
init_tree_desc(&trees[1], tree->buffer, tree->size);
613+
614+
ret = unpack_trees(2, trees, &topts);
615+
if (ret == -1) {
616+
/*
617+
* Unpack couldn't do a trivial merge; either
618+
* give up or do a real merge, depending on
619+
* whether the merge flag was used.
620+
*/
621+
struct tree *result;
622+
struct tree *work;
623+
struct merge_options o;
624+
if (!opts->merge)
625+
return 1;
626+
627+
/*
628+
* Without old->commit, the below is the same as
629+
* the two-tree unpack we already tried and failed.
630+
*/
631+
if (!old->commit)
632+
return 1;
633+
634+
/* Do more real merge */
635+
636+
/*
637+
* We update the index fully, then write the
638+
* tree from the index, then merge the new
639+
* branch with the current tree, with the old
640+
* branch as the base. Then we reset the index
641+
* (but not the working tree) to the new
642+
* branch, leaving the working tree as the
643+
* merged version, but skipping unmerged
644+
* entries in the index.
645+
*/
646+
647+
add_files_to_cache(NULL, NULL, 0, 0);
648+
/*
649+
* NEEDSWORK: carrying over local changes
650+
* when branches have different end-of-line
651+
* normalization (or clean+smudge rules) is
652+
* a pain; plumb in an option to set
653+
* o.renormalize?
654+
*/
655+
init_merge_options(&o);
656+
o.verbosity = 0;
657+
work = write_tree_from_memory(&o);
658+
659+
ret = reset_tree(new->commit->tree, opts, 1,
660+
writeout_error);
661+
if (ret)
662+
return ret;
663+
o.ancestor = old->name;
664+
o.branch1 = new->name;
665+
o.branch2 = "local";
666+
merge_trees(&o, new->commit->tree, work,
667+
old->commit->tree, &result);
668+
ret = reset_tree(new->commit->tree, opts, 0,
669+
writeout_error);
670+
if (ret)
671+
return ret;
672+
}
577673
}
578-
}
579674

580-
if (!active_cache_tree)
581-
active_cache_tree = cache_tree();
675+
if (!active_cache_tree)
676+
active_cache_tree = cache_tree();
582677

583-
if (!cache_tree_fully_valid(active_cache_tree))
584-
cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR);
678+
if (!cache_tree_fully_valid(active_cache_tree))
679+
cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR);
585680

586-
if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
587-
die(_("unable to write new index file"));
681+
if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
682+
die(_("unable to write new index file"));
683+
}
588684

589685
if (!opts->force && !opts->quiet)
590686
show_local_changes(&new->commit->object, &opts->diff_options);
@@ -825,26 +921,10 @@ static int switch_branches(const struct checkout_opts *opts,
825921
parse_commit_or_die(new->commit);
826922
}
827923

828-
/*
829-
* Optimize the performance of "git checkout foo" by skipping the call
830-
* to merge_working_tree. Make this as restrictive as possible, only
831-
* checkout a new branch with the current commit. Any other options force
832-
* it through the old path.
833-
*/
834-
if (!gvfs_config_is_set(GVFS_SKIP_MERGE_IN_CHECKOUT)
835-
|| !old.commit || !new->commit
836-
|| oidcmp(&old.commit->object.oid, &new->commit->object.oid)
837-
|| !opts->new_branch || opts->new_branch_force || opts->new_orphan_branch
838-
|| opts->patch_mode || opts->merge || opts->force || opts->force_detach
839-
|| opts->writeout_stage || !opts->overwrite_ignore
840-
|| opts->ignore_skipworktree || opts->ignore_other_worktrees
841-
|| opts->new_branch_log || opts->branch_exists || opts->prefix
842-
|| opts->source_tree) {
843-
ret = merge_working_tree(opts, &old, new, &writeout_error);
844-
if (ret) {
845-
free(path_to_free);
846-
return ret;
847-
}
924+
ret = merge_working_tree(opts, &old, new, &writeout_error);
925+
if (ret) {
926+
free(path_to_free);
927+
return ret;
848928
}
849929

850930
if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)

0 commit comments

Comments
 (0)