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>
maint
Nguyễn Thái Ngọc Duy 2019-03-29 17:39:16 +07:00 committed by Junio C Hamano
parent c45f0f525d
commit 1806c29f2e
1 changed files with 31 additions and 8 deletions

View File

@ -58,6 +58,7 @@ struct checkout_opts {
int switch_branch_doing_nothing_is_ok; int switch_branch_doing_nothing_is_ok;
int only_merge_on_switching_branches; int only_merge_on_switching_branches;
int can_switch_when_in_progress; int can_switch_when_in_progress;
int orphan_from_empty_tree;


const char *new_branch; const char *new_branch;
const char *new_branch_force; const char *new_branch_force;
@ -568,15 +569,21 @@ static int merge_working_tree(const struct checkout_opts *opts,
{ {
int ret; int ret;
struct lock_file lock_file = LOCK_INIT; struct lock_file lock_file = LOCK_INIT;
struct tree *new_tree;


hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
if (read_cache_preload(NULL) < 0) if (read_cache_preload(NULL) < 0)
return error(_("index file corrupt")); return error(_("index file corrupt"));


resolve_undo_clear(); resolve_undo_clear();
if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
if (new_branch_info->commit)
BUG("'switch --orphan' should never accept a commit as starting point");
new_tree = parse_tree_indirect(the_hash_algo->empty_tree);
} else
new_tree = get_commit_tree(new_branch_info->commit);
if (opts->discard_changes) { if (opts->discard_changes) {
ret = reset_tree(get_commit_tree(new_branch_info->commit), ret = reset_tree(new_tree, opts, 1, writeout_error);
opts, 1, writeout_error);
if (ret) if (ret)
return ret; return ret;
} else { } else {
@ -614,7 +621,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
&old_branch_info->commit->object.oid : &old_branch_info->commit->object.oid :
the_hash_algo->empty_tree); the_hash_algo->empty_tree);
init_tree_desc(&trees[0], tree->buffer, tree->size); init_tree_desc(&trees[0], tree->buffer, tree->size);
tree = parse_tree_indirect(&new_branch_info->commit->object.oid); parse_tree(new_tree);
tree = new_tree;
init_tree_desc(&trees[1], tree->buffer, tree->size); init_tree_desc(&trees[1], tree->buffer, tree->size);


ret = unpack_trees(2, trees, &topts); ret = unpack_trees(2, trees, &topts);
@ -663,7 +671,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
o.verbosity = 0; o.verbosity = 0;
work = write_tree_from_memory(&o); work = write_tree_from_memory(&o);


ret = reset_tree(get_commit_tree(new_branch_info->commit), ret = reset_tree(new_tree,
opts, 1, opts, 1,
writeout_error); writeout_error);
if (ret) if (ret)
@ -672,13 +680,13 @@ static int merge_working_tree(const struct checkout_opts *opts,
o.branch1 = new_branch_info->name; o.branch1 = new_branch_info->name;
o.branch2 = "local"; o.branch2 = "local";
ret = merge_trees(&o, ret = merge_trees(&o,
get_commit_tree(new_branch_info->commit), new_tree,
work, work,
get_commit_tree(old_branch_info->commit), get_commit_tree(old_branch_info->commit),
&result); &result);
if (ret < 0) if (ret < 0)
exit(128); exit(128);
ret = reset_tree(get_commit_tree(new_branch_info->commit), ret = reset_tree(new_tree,
opts, 0, opts, 0,
writeout_error); writeout_error);
strbuf_release(&o.obuf); strbuf_release(&o.obuf);
@ -696,7 +704,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die(_("unable to write new index file")); die(_("unable to write new index file"));


if (!opts->discard_changes && !opts->quiet) if (!opts->discard_changes && !opts->quiet && new_branch_info->commit)
show_local_changes(&new_branch_info->commit->object, &opts->diff_options); show_local_changes(&new_branch_info->commit->object, &opts->diff_options);


return 0; return 0;
@ -897,7 +905,10 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
add_pending_object(&revs, object, oid_to_hex(&object->oid)); add_pending_object(&revs, object, oid_to_hex(&object->oid));


for_each_ref(add_pending_uninteresting_ref, &revs); for_each_ref(add_pending_uninteresting_ref, &revs);
add_pending_oid(&revs, "HEAD", &new_commit->object.oid, UNINTERESTING); if (new_commit)
add_pending_oid(&revs, "HEAD",
&new_commit->object.oid,
UNINTERESTING);


if (prepare_revision_walk(&revs)) if (prepare_revision_walk(&revs))
die(_("internal error in revision walk")); die(_("internal error in revision walk"));
@ -932,6 +943,14 @@ static int switch_branches(const struct checkout_opts *opts,
if (old_branch_info.path) if (old_branch_info.path)
skip_prefix(old_branch_info.path, "refs/heads/", &old_branch_info.name); skip_prefix(old_branch_info.path, "refs/heads/", &old_branch_info.name);


if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
if (new_branch_info->name)
BUG("'switch --orphan' should never accept a commit as starting point");
new_branch_info->commit = NULL;
new_branch_info->name = "(empty)";
do_merge = 1;
}

if (!new_branch_info->name) { if (!new_branch_info->name) {
new_branch_info->name = "HEAD"; new_branch_info->name = "HEAD";
new_branch_info->commit = old_branch_info.commit; new_branch_info->commit = old_branch_info.commit;
@ -1268,6 +1287,8 @@ static int checkout_branch(struct checkout_opts *opts,
if (opts->new_orphan_branch) { if (opts->new_orphan_branch) {
if (opts->track != BRANCH_TRACK_UNSPECIFIED) if (opts->track != BRANCH_TRACK_UNSPECIFIED)
die(_("'%s' cannot be used with '%s'"), "--orphan", "-t"); die(_("'%s' cannot be used with '%s'"), "--orphan", "-t");
if (opts->orphan_from_empty_tree && new_branch_info->name)
die(_("'%s' cannot take <start-point>"), "--orphan");
} else if (opts->force_detach) { } else if (opts->force_detach) {
if (opts->track != BRANCH_TRACK_UNSPECIFIED) if (opts->track != BRANCH_TRACK_UNSPECIFIED)
die(_("'%s' cannot be used with '%s'"), "--detach", "-t"); die(_("'%s' cannot be used with '%s'"), "--detach", "-t");
@ -1553,6 +1574,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
opts.accept_pathspec = 1; opts.accept_pathspec = 1;
opts.implicit_detach = 1; opts.implicit_detach = 1;
opts.can_switch_when_in_progress = 1; opts.can_switch_when_in_progress = 1;
opts.orphan_from_empty_tree = 0;


options = parse_options_dup(checkout_options); options = parse_options_dup(checkout_options);
options = add_common_options(&opts, options); options = add_common_options(&opts, options);
@ -1589,6 +1611,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
opts.only_merge_on_switching_branches = 1; opts.only_merge_on_switching_branches = 1;
opts.implicit_detach = 0; opts.implicit_detach = 0;
opts.can_switch_when_in_progress = 0; opts.can_switch_when_in_progress = 0;
opts.orphan_from_empty_tree = 1;


options = parse_options_dup(switch_options); options = parse_options_dup(switch_options);
options = add_common_options(&opts, options); options = add_common_options(&opts, options);