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_optsmaint
commit
10174da9f1
|
@ -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::
|
||||||
|
|
||||||
|
|
|
@ -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"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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' '
|
||||||
|
|
Loading…
Reference in New Issue