switch: make --orphan switch to an empty tree

Switching and creating branches always involves knowing the <start-point> to begin the new branch from. Sometimes, people want to create a new branch that does not have any commits yet; --orphan is a flag to allow that. --orphan overrides the default of HEAD for <start-point> instead causing us to start from an empty history with all tracked files removed from the index and working tree. The use of --orphan is incompatible with specifying a <start-point>. A note on the implementation. An alternative is just create a dummy commit in-core with empty tree and switch to it. But there's a chance the commit's SHA-1 may end up somewhere permanent like reflog. It's best to make sure "commit" pointer is NULL to avoid it. Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Nguyễn Thái Ngọc Duy committed Mar 29, 2019 at 17:39 UTC 1806c29f2eb5f7e4b5952f4fcc75f2afe279f1a4
1 file changed +31 -8
builtin/checkout.c
+31 -8
@@ -58,6 +58,7 @@ struct checkout_opts {
58 int switch_branch_doing_nothing_is_ok;
59 int only_merge_on_switching_branches;
60 int can_switch_when_in_progress;
61 + int orphan_from_empty_tree;
62
63 const char *new_branch;
64 const char *new_branch_force;
@@ -568,15 +569,21 @@ static int merge_working_tree(const struct checkout_opts *opts,
569 {
570 int ret;
571 struct lock_file lock_file = LOCK_INIT;
572 + struct tree *new_tree;
573
574 hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
575 if (read_cache_preload(NULL) < 0)
576 return error(_("index file corrupt"));
577
578 resolve_undo_clear();
579 + if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
580 + if (new_branch_info->commit)
581 + BUG("'switch --orphan' should never accept a commit as starting point");
582 + new_tree = parse_tree_indirect(the_hash_algo->empty_tree);
583 + } else
584 + new_tree = get_commit_tree(new_branch_info->commit);
585 if (opts->discard_changes) {
578 - ret = reset_tree(get_commit_tree(new_branch_info->commit),
579 - opts, 1, writeout_error);
586 + ret = reset_tree(new_tree, opts, 1, writeout_error);
587 if (ret)
588 return ret;
589 } else {
@@ -614,7 +621,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
621 &old_branch_info->commit->object.oid :
622 the_hash_algo->empty_tree);
623 init_tree_desc(&trees[0], tree->buffer, tree->size);
617 - tree = parse_tree_indirect(&new_branch_info->commit->object.oid);
624 + parse_tree(new_tree);
625 + tree = new_tree;
626 init_tree_desc(&trees[1], tree->buffer, tree->size);
627
628 ret = unpack_trees(2, trees, &topts);
@@ -663,7 +671,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
671 o.verbosity = 0;
672 work = write_tree_from_memory(&o);
673
666 - ret = reset_tree(get_commit_tree(new_branch_info->commit),
674 + ret = reset_tree(new_tree,
675 opts, 1,
676 writeout_error);
677 if (ret)
@@ -672,13 +680,13 @@ static int merge_working_tree(const struct checkout_opts *opts,
680 o.branch1 = new_branch_info->name;
681 o.branch2 = "local";
682 ret = merge_trees(&o,
675 - get_commit_tree(new_branch_info->commit),
683 + new_tree,
684 work,
685 get_commit_tree(old_branch_info->commit),
686 &result);
687 if (ret < 0)
688 exit(128);
681 - ret = reset_tree(get_commit_tree(new_branch_info->commit),
689 + ret = reset_tree(new_tree,
690 opts, 0,
691 writeout_error);
692 strbuf_release(&o.obuf);
@@ -696,7 +704,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
704 if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
705 die(_("unable to write new index file"));
706
699 - if (!opts->discard_changes && !opts->quiet)
707 + if (!opts->discard_changes && !opts->quiet && new_branch_info->commit)
708 show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
709
710 return 0;
@@ -897,7 +905,10 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
905 add_pending_object(&revs, object, oid_to_hex(&object->oid));
906
907 for_each_ref(add_pending_uninteresting_ref, &revs);
900 - add_pending_oid(&revs, "HEAD", &new_commit->object.oid, UNINTERESTING);
908 + if (new_commit)
909 + add_pending_oid(&revs, "HEAD",
910 + &new_commit->object.oid,
911 + UNINTERESTING);
912
913 if (prepare_revision_walk(&revs))
914 die(_("internal error in revision walk"));
@@ -932,6 +943,14 @@ static int switch_branches(const struct checkout_opts *opts,
943 if (old_branch_info.path)
944 skip_prefix(old_branch_info.path, "refs/heads/", &old_branch_info.name);
945
946 + if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
947 + if (new_branch_info->name)
948 + BUG("'switch --orphan' should never accept a commit as starting point");
949 + new_branch_info->commit = NULL;
950 + new_branch_info->name = "(empty)";
951 + do_merge = 1;
952 + }
953 +
954 if (!new_branch_info->name) {
955 new_branch_info->name = "HEAD";
956 new_branch_info->commit = old_branch_info.commit;
@@ -1268,6 +1287,8 @@ static int checkout_branch(struct checkout_opts *opts,
1287 if (opts->new_orphan_branch) {
1288 if (opts->track != BRANCH_TRACK_UNSPECIFIED)
1289 die(_("'%s' cannot be used with '%s'"), "--orphan", "-t");
1290 + if (opts->orphan_from_empty_tree && new_branch_info->name)
1291 + die(_("'%s' cannot take <start-point>"), "--orphan");
1292 } else if (opts->force_detach) {
1293 if (opts->track != BRANCH_TRACK_UNSPECIFIED)
1294 die(_("'%s' cannot be used with '%s'"), "--detach", "-t");
@@ -1553,6 +1574,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
1574 opts.accept_pathspec = 1;
1575 opts.implicit_detach = 1;
1576 opts.can_switch_when_in_progress = 1;
1577 + opts.orphan_from_empty_tree = 0;
1578
1579 options = parse_options_dup(checkout_options);
1580 options = add_common_options(&opts, options);
@@ -1589,6 +1611,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
1611 opts.only_merge_on_switching_branches = 1;
1612 opts.implicit_detach = 0;
1613 opts.can_switch_when_in_progress = 0;
1614 + opts.orphan_from_empty_tree = 1;
1615
1616 options = parse_options_dup(switch_options);
1617 options = add_common_options(&opts, options);