Skip to content

Commit 10174da

Browse files
committed
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
2 parents 352cf6c + f60a7b7 commit 10174da

File tree

3 files changed

+100
-36
lines changed

3 files changed

+100
-36
lines changed

Documentation/git-worktree.txt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,13 @@ $ git worktree add --track -b <branch> <path> <remote>/<branch>
6161
------------
6262
+
6363
If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
64-
then, as a convenience, a new branch based at HEAD is created automatically,
65-
as if `-b $(basename <path>)` was specified.
64+
then, as a convenience, the new worktree is associated with a branch
65+
(call it `<branch>`) named after `$(basename <path>)`. If `<branch>`
66+
doesn't exist, a new branch based on HEAD is automatically created as
67+
if `-b <branch>` was given. If `<branch>` does exist, it will be
68+
checked out in the new worktree, if it's not checked out anywhere
69+
else, otherwise the command will refuse to create the worktree (unless
70+
`--force` is used).
6671

6772
list::
6873

builtin/worktree.c

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ struct add_opts {
2929
int detach;
3030
int checkout;
3131
int keep_locked;
32-
const char *new_branch;
33-
int force_new_branch;
3432
};
3533

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

301-
fprintf_ln(stderr, _("Preparing %s (identifier %s)"), path, name);
302-
303299
argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
304300
argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
305301
cp.git_cmd = 1;
@@ -366,18 +362,75 @@ static int add_worktree(const char *path, const char *refname,
366362
return ret;
367363
}
368364

365+
static void print_preparing_worktree_line(int detach,
366+
const char *branch,
367+
const char *new_branch,
368+
int force_new_branch)
369+
{
370+
if (force_new_branch) {
371+
struct commit *commit = lookup_commit_reference_by_name(new_branch);
372+
if (!commit)
373+
printf_ln(_("Preparing worktree (new branch '%s')"), new_branch);
374+
else
375+
printf_ln(_("Preparing worktree (resetting branch '%s'; was at %s)"),
376+
new_branch,
377+
find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV));
378+
} else if (new_branch) {
379+
printf_ln(_("Preparing worktree (new branch '%s')"), new_branch);
380+
} else {
381+
struct strbuf s = STRBUF_INIT;
382+
if (!detach && !strbuf_check_branch_ref(&s, branch) &&
383+
ref_exists(s.buf))
384+
printf_ln(_("Preparing worktree (checking out '%s')"),
385+
branch);
386+
else {
387+
struct commit *commit = lookup_commit_reference_by_name(branch);
388+
if (!commit)
389+
die(_("invalid reference: %s"), branch);
390+
printf_ln(_("Preparing worktree (detached HEAD %s)"),
391+
find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV));
392+
}
393+
strbuf_release(&s);
394+
}
395+
}
396+
397+
static const char *dwim_branch(const char *path, const char **new_branch)
398+
{
399+
int n;
400+
const char *s = worktree_basename(path, &n);
401+
const char *branchname = xstrndup(s, n);
402+
struct strbuf ref = STRBUF_INIT;
403+
404+
UNLEAK(branchname);
405+
if (!strbuf_check_branch_ref(&ref, branchname) &&
406+
ref_exists(ref.buf)) {
407+
strbuf_release(&ref);
408+
return branchname;
409+
}
410+
411+
*new_branch = branchname;
412+
if (guess_remote) {
413+
struct object_id oid;
414+
const char *remote =
415+
unique_tracking_name(*new_branch, &oid);
416+
return remote;
417+
}
418+
return NULL;
419+
}
420+
369421
static int add(int ac, const char **av, const char *prefix)
370422
{
371423
struct add_opts opts;
372424
const char *new_branch_force = NULL;
373425
char *path;
374426
const char *branch;
427+
const char *new_branch = NULL;
375428
const char *opt_track = NULL;
376429
struct option options[] = {
377430
OPT__FORCE(&opts.force,
378431
N_("checkout <branch> even if already checked out in other worktree"),
379432
PARSE_OPT_NOCOMPLETE),
380-
OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
433+
OPT_STRING('b', NULL, &new_branch, N_("branch"),
381434
N_("create a new branch")),
382435
OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
383436
N_("create or reset a branch")),
@@ -395,7 +448,7 @@ static int add(int ac, const char **av, const char *prefix)
395448
memset(&opts, 0, sizeof(opts));
396449
opts.checkout = 1;
397450
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
398-
if (!!opts.detach + !!opts.new_branch + !!new_branch_force > 1)
451+
if (!!opts.detach + !!new_branch + !!new_branch_force > 1)
399452
die(_("-b, -B, and --detach are mutually exclusive"));
400453
if (ac < 1 || ac > 2)
401454
usage_with_options(worktree_usage, options);
@@ -406,33 +459,25 @@ static int add(int ac, const char **av, const char *prefix)
406459
if (!strcmp(branch, "-"))
407460
branch = "@{-1}";
408461

409-
opts.force_new_branch = !!new_branch_force;
410-
if (opts.force_new_branch) {
462+
if (new_branch_force) {
411463
struct strbuf symref = STRBUF_INIT;
412464

413-
opts.new_branch = new_branch_force;
465+
new_branch = new_branch_force;
414466

415467
if (!opts.force &&
416-
!strbuf_check_branch_ref(&symref, opts.new_branch) &&
468+
!strbuf_check_branch_ref(&symref, new_branch) &&
417469
ref_exists(symref.buf))
418470
die_if_checked_out(symref.buf, 0);
419471
strbuf_release(&symref);
420472
}
421473

422-
if (ac < 2 && !opts.new_branch && !opts.detach) {
423-
int n;
424-
const char *s = worktree_basename(path, &n);
425-
opts.new_branch = xstrndup(s, n);
426-
if (guess_remote) {
427-
struct object_id oid;
428-
const char *remote =
429-
unique_tracking_name(opts.new_branch, &oid);
430-
if (remote)
431-
branch = remote;
432-
}
474+
if (ac < 2 && !new_branch && !opts.detach) {
475+
const char *s = dwim_branch(path, &new_branch);
476+
if (s)
477+
branch = s;
433478
}
434479

435-
if (ac == 2 && !opts.new_branch && !opts.detach) {
480+
if (ac == 2 && !new_branch && !opts.detach) {
436481
struct object_id oid;
437482
struct commit *commit;
438483
const char *remote;
@@ -441,25 +486,27 @@ static int add(int ac, const char **av, const char *prefix)
441486
if (!commit) {
442487
remote = unique_tracking_name(branch, &oid);
443488
if (remote) {
444-
opts.new_branch = branch;
489+
new_branch = branch;
445490
branch = remote;
446491
}
447492
}
448493
}
449494

450-
if (opts.new_branch) {
495+
print_preparing_worktree_line(opts.detach, branch, new_branch, !!new_branch_force);
496+
497+
if (new_branch) {
451498
struct child_process cp = CHILD_PROCESS_INIT;
452499
cp.git_cmd = 1;
453500
argv_array_push(&cp.args, "branch");
454-
if (opts.force_new_branch)
501+
if (new_branch_force)
455502
argv_array_push(&cp.args, "--force");
456-
argv_array_push(&cp.args, opts.new_branch);
503+
argv_array_push(&cp.args, new_branch);
457504
argv_array_push(&cp.args, branch);
458505
if (opt_track)
459506
argv_array_push(&cp.args, opt_track);
460507
if (run_command(&cp))
461508
return -1;
462-
branch = opts.new_branch;
509+
branch = new_branch;
463510
} else if (opt_track) {
464511
die(_("--[no-]track can only be used if a new branch is created"));
465512
}

t/t2025-worktree-add.sh

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,25 @@ test_expect_success '"add" with <branch> omitted' '
198198
test_cmp_rev HEAD bat
199199
'
200200

201-
test_expect_success '"add" auto-vivify does not clobber existing branch' '
202-
test_commit c1 &&
203-
test_commit c2 &&
204-
git branch precious HEAD~1 &&
205-
test_must_fail git worktree add precious &&
206-
test_cmp_rev HEAD~1 precious &&
207-
test_path_is_missing precious
201+
test_expect_success '"add" checks out existing branch of dwimd name' '
202+
git branch dwim HEAD~1 &&
203+
git worktree add dwim &&
204+
test_cmp_rev HEAD~1 dwim &&
205+
(
206+
cd dwim &&
207+
test_cmp_rev HEAD dwim
208+
)
209+
'
210+
211+
test_expect_success '"add <path>" dwim fails with checked out branch' '
212+
git checkout -b test-branch &&
213+
test_must_fail git worktree add test-branch &&
214+
test_path_is_missing test-branch
215+
'
216+
217+
test_expect_success '"add --force" with existing dwimd name doesnt die' '
218+
git checkout test-branch &&
219+
git worktree add --force test-branch
208220
'
209221

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

0 commit comments

Comments
 (0)