Merge branch 'tg/worktree-add-existing-branch'

"git worktree add" learned to check out an existing branch.

* tg/worktree-add-existing-branch:
  worktree: teach "add" to check out existing branches
  worktree: factor out dwim_branch function
  worktree: improve message when creating a new worktree
  worktree: remove extra members from struct add_opts
maint
Junio C Hamano 2018-05-23 14:38:18 +09:00
commit 10174da9f1
3 changed files with 100 additions and 36 deletions

View File

@ -61,8 +61,13 @@ $ git worktree add --track -b <branch> <path> <remote>/<branch>
------------ ------------
+ +
If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used, If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
then, as a convenience, a new branch based at HEAD is created automatically, then, as a convenience, the new worktree is associated with a branch
as if `-b $(basename <path>)` was specified. (call it `<branch>`) named after `$(basename <path>)`. If `<branch>`
doesn't exist, a new branch based on HEAD is automatically created as
if `-b <branch>` was given. If `<branch>` does exist, it will be
checked out in the new worktree, if it's not checked out anywhere
else, otherwise the command will refuse to create the worktree (unless
`--force` is used).


list:: list::



View File

@ -29,8 +29,6 @@ struct add_opts {
int detach; int detach;
int checkout; int checkout;
int keep_locked; int keep_locked;
const char *new_branch;
int force_new_branch;
}; };


static int show_only; static int show_only;
@ -298,8 +296,6 @@ static int add_worktree(const char *path, const char *refname,
strbuf_addf(&sb, "%s/commondir", sb_repo.buf); strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
write_file(sb.buf, "../.."); write_file(sb.buf, "../..");


fprintf_ln(stderr, _("Preparing %s (identifier %s)"), path, name);

argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf); argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path); argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
cp.git_cmd = 1; cp.git_cmd = 1;
@ -366,18 +362,75 @@ done:
return ret; return ret;
} }


static void print_preparing_worktree_line(int detach,
const char *branch,
const char *new_branch,
int force_new_branch)
{
if (force_new_branch) {
struct commit *commit = lookup_commit_reference_by_name(new_branch);
if (!commit)
printf_ln(_("Preparing worktree (new branch '%s')"), new_branch);
else
printf_ln(_("Preparing worktree (resetting branch '%s'; was at %s)"),
new_branch,
find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV));
} else if (new_branch) {
printf_ln(_("Preparing worktree (new branch '%s')"), new_branch);
} else {
struct strbuf s = STRBUF_INIT;
if (!detach && !strbuf_check_branch_ref(&s, branch) &&
ref_exists(s.buf))
printf_ln(_("Preparing worktree (checking out '%s')"),
branch);
else {
struct commit *commit = lookup_commit_reference_by_name(branch);
if (!commit)
die(_("invalid reference: %s"), branch);
printf_ln(_("Preparing worktree (detached HEAD %s)"),
find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV));
}
strbuf_release(&s);
}
}

static const char *dwim_branch(const char *path, const char **new_branch)
{
int n;
const char *s = worktree_basename(path, &n);
const char *branchname = xstrndup(s, n);
struct strbuf ref = STRBUF_INIT;

UNLEAK(branchname);
if (!strbuf_check_branch_ref(&ref, branchname) &&
ref_exists(ref.buf)) {
strbuf_release(&ref);
return branchname;
}

*new_branch = branchname;
if (guess_remote) {
struct object_id oid;
const char *remote =
unique_tracking_name(*new_branch, &oid);
return remote;
}
return NULL;
}

static int add(int ac, const char **av, const char *prefix) static int add(int ac, const char **av, const char *prefix)
{ {
struct add_opts opts; struct add_opts opts;
const char *new_branch_force = NULL; const char *new_branch_force = NULL;
char *path; char *path;
const char *branch; const char *branch;
const char *new_branch = NULL;
const char *opt_track = NULL; const char *opt_track = NULL;
struct option options[] = { struct option options[] = {
OPT__FORCE(&opts.force, OPT__FORCE(&opts.force,
N_("checkout <branch> even if already checked out in other worktree"), N_("checkout <branch> even if already checked out in other worktree"),
PARSE_OPT_NOCOMPLETE), PARSE_OPT_NOCOMPLETE),
OPT_STRING('b', NULL, &opts.new_branch, N_("branch"), OPT_STRING('b', NULL, &new_branch, N_("branch"),
N_("create a new branch")), N_("create a new branch")),
OPT_STRING('B', NULL, &new_branch_force, N_("branch"), OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
N_("create or reset a branch")), N_("create or reset a branch")),
@ -395,7 +448,7 @@ static int add(int ac, const char **av, const char *prefix)
memset(&opts, 0, sizeof(opts)); memset(&opts, 0, sizeof(opts));
opts.checkout = 1; opts.checkout = 1;
ac = parse_options(ac, av, prefix, options, worktree_usage, 0); ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
if (!!opts.detach + !!opts.new_branch + !!new_branch_force > 1) if (!!opts.detach + !!new_branch + !!new_branch_force > 1)
die(_("-b, -B, and --detach are mutually exclusive")); die(_("-b, -B, and --detach are mutually exclusive"));
if (ac < 1 || ac > 2) if (ac < 1 || ac > 2)
usage_with_options(worktree_usage, options); usage_with_options(worktree_usage, options);
@ -406,33 +459,25 @@ static int add(int ac, const char **av, const char *prefix)
if (!strcmp(branch, "-")) if (!strcmp(branch, "-"))
branch = "@{-1}"; branch = "@{-1}";


opts.force_new_branch = !!new_branch_force; if (new_branch_force) {
if (opts.force_new_branch) {
struct strbuf symref = STRBUF_INIT; struct strbuf symref = STRBUF_INIT;


opts.new_branch = new_branch_force; new_branch = new_branch_force;


if (!opts.force && if (!opts.force &&
!strbuf_check_branch_ref(&symref, opts.new_branch) && !strbuf_check_branch_ref(&symref, new_branch) &&
ref_exists(symref.buf)) ref_exists(symref.buf))
die_if_checked_out(symref.buf, 0); die_if_checked_out(symref.buf, 0);
strbuf_release(&symref); strbuf_release(&symref);
} }


if (ac < 2 && !opts.new_branch && !opts.detach) { if (ac < 2 && !new_branch && !opts.detach) {
int n; const char *s = dwim_branch(path, &new_branch);
const char *s = worktree_basename(path, &n); if (s)
opts.new_branch = xstrndup(s, n); branch = s;
if (guess_remote) {
struct object_id oid;
const char *remote =
unique_tracking_name(opts.new_branch, &oid);
if (remote)
branch = remote;
}
} }


if (ac == 2 && !opts.new_branch && !opts.detach) { if (ac == 2 && !new_branch && !opts.detach) {
struct object_id oid; struct object_id oid;
struct commit *commit; struct commit *commit;
const char *remote; const char *remote;
@ -441,25 +486,27 @@ static int add(int ac, const char **av, const char *prefix)
if (!commit) { if (!commit) {
remote = unique_tracking_name(branch, &oid); remote = unique_tracking_name(branch, &oid);
if (remote) { if (remote) {
opts.new_branch = branch; new_branch = branch;
branch = remote; branch = remote;
} }
} }
} }


if (opts.new_branch) { print_preparing_worktree_line(opts.detach, branch, new_branch, !!new_branch_force);

if (new_branch) {
struct child_process cp = CHILD_PROCESS_INIT; struct child_process cp = CHILD_PROCESS_INIT;
cp.git_cmd = 1; cp.git_cmd = 1;
argv_array_push(&cp.args, "branch"); argv_array_push(&cp.args, "branch");
if (opts.force_new_branch) if (new_branch_force)
argv_array_push(&cp.args, "--force"); argv_array_push(&cp.args, "--force");
argv_array_push(&cp.args, opts.new_branch); argv_array_push(&cp.args, new_branch);
argv_array_push(&cp.args, branch); argv_array_push(&cp.args, branch);
if (opt_track) if (opt_track)
argv_array_push(&cp.args, opt_track); argv_array_push(&cp.args, opt_track);
if (run_command(&cp)) if (run_command(&cp))
return -1; return -1;
branch = opts.new_branch; branch = new_branch;
} else if (opt_track) { } else if (opt_track) {
die(_("--[no-]track can only be used if a new branch is created")); die(_("--[no-]track can only be used if a new branch is created"));
} }

View File

@ -198,13 +198,25 @@ test_expect_success '"add" with <branch> omitted' '
test_cmp_rev HEAD bat test_cmp_rev HEAD bat
' '


test_expect_success '"add" auto-vivify does not clobber existing branch' ' test_expect_success '"add" checks out existing branch of dwimd name' '
test_commit c1 && git branch dwim HEAD~1 &&
test_commit c2 && git worktree add dwim &&
git branch precious HEAD~1 && test_cmp_rev HEAD~1 dwim &&
test_must_fail git worktree add precious && (
test_cmp_rev HEAD~1 precious && cd dwim &&
test_path_is_missing precious test_cmp_rev HEAD dwim
)
'

test_expect_success '"add <path>" dwim fails with checked out branch' '
git checkout -b test-branch &&
test_must_fail git worktree add test-branch &&
test_path_is_missing test-branch
'

test_expect_success '"add --force" with existing dwimd name doesnt die' '
git checkout test-branch &&
git worktree add --force test-branch
' '


test_expect_success '"add" no auto-vivify with --detach and <branch> omitted' ' test_expect_success '"add" no auto-vivify with --detach and <branch> omitted' '