Merge branch 'rs/worktree-add-basename-fixes' into jch

The string extraction logic for the branch name and worktree name
from the given path in 'git worktree add' has been corrected and
simplified to avoid out-of-bounds reads and improper handling of
trailing slashes.

* rs/worktree-add-basename-fixes:
  worktree add: let worktree_basename() return string copy
  worktree add: trim slashes when deriving branch name from path
  worktree add: reject separator-only path
  worktree add: don't read out of bounds in worktree_basename()
jch
Junio C Hamano 2026-08-31 11:12:02 -07:00
commit edcfdf8463
2 changed files with 31 additions and 19 deletions

View File

@ -294,7 +294,7 @@ static void remove_junk_on_signal(int signo)
raise(signo);
}

static const char *worktree_basename(const char *path, int *olen)
static char *worktree_basename_dup(const char *path)
{
const char *name;
int len;
@ -303,14 +303,11 @@ static const char *worktree_basename(const char *path, int *olen)
while (len && is_dir_sep(path[len - 1]))
len--;

for (name = path + len - 1; name > path; name--)
if (is_dir_sep(*name)) {
name++;
break;
}
name = path + len;
while (name > path && !is_dir_sep(name[-1]))
name--;

*olen = len;
return name;
return xmemdupz(name, path + len - name);
}

/* check that path is viable location for worktree */
@ -464,6 +461,7 @@ static int add_worktree(const char *path, const char *refname,
struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
struct strbuf sb = STRBUF_INIT;
const char *name;
char *name_to_free = NULL;
struct strvec child_env = STRVEC_INIT;
unsigned int counter = 0;
int len, ret;
@ -491,12 +489,12 @@ static int add_worktree(const char *path, const char *refname,
if (!commit && !opts->orphan)
die(_("invalid reference: %s"), refname);

name = worktree_basename(path, &len);
strbuf_add(&sb, name, path + len - name);
sanitize_refname_component(sb.buf, &sb_name);
name = name_to_free = worktree_basename_dup(path);
if (!*name)
die(_("invalid path '%s'"), path);
sanitize_refname_component(name, &sb_name);
if (!sb_name.len)
BUG("How come '%s' becomes empty after sanitization?", sb.buf);
strbuf_reset(&sb);
BUG("How come '%s' becomes empty after sanitization?", name);
name = sb_name.buf;
repo_git_path_replace(the_repository, &sb_repo, "worktrees/%s", name);
len = sb_repo.len;
@ -630,6 +628,7 @@ done:
strbuf_release(&sb_git);
strbuf_release(&sb_name);
free_worktree(wt);
free(name_to_free);
return ret;
}

@ -785,10 +784,8 @@ static void advise_disambiguating_remotes(const char *path, const char *branch,

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

branch_exists = !check_branch_ref(the_repository, &ref, branchname) &&
@ -909,9 +906,7 @@ static int add(int ac, const char **av, const char *prefix,
}

if (opts.orphan && !new_branch) {
int n;
const char *s = worktree_basename(path, &n);
new_branch = new_branch_to_free = xstrndup(s, n);
new_branch = new_branch_to_free = worktree_basename_dup(path);
} else if (opts.orphan) {
; /* no-op */
} else if (opts.detach) {

View File

@ -46,6 +46,10 @@ test_expect_success '"add" refuses to checkout locked branch' '
test_path_is_missing .git/worktrees/zere
'

test_expect_success '"add" rejects an empty path' '
test_must_fail git worktree add "" HEAD
'

test_expect_success 'checking out paths not complaining about linked checkouts' '
(
cd existing_empty &&
@ -294,6 +298,11 @@ test_expect_success '"add" with <branch> omitted' '
test_cmp_rev HEAD bat
'

test_expect_success '"add" with trailing slash and <branch> omitted' '
git worktree add waffle/bit/ &&
test_cmp_rev HEAD bit
'

test_expect_success '"add" checks out existing branch of dwimd name' '
git branch dwim HEAD~1 &&
git worktree add dwim &&
@ -384,6 +393,14 @@ test_expect_success '"add --orphan (no -b)"' '
test_cmp expected actual
'

test_expect_success '"add --orphan with trailing slash (no -b)"' '
test_when_finished "git worktree remove -f -f neworphan" &&
git worktree add --orphan ./neworphan/ &&
echo refs/heads/neworphan >expected &&
git -C neworphan symbolic-ref HEAD >actual &&
test_cmp expected actual
'

test_expect_success '"add --orphan --quiet"' '
test_when_finished "git worktree remove -f -f orphandir" &&
git worktree add --quiet --orphan -b neworphan orphandir 2>log.actual &&