Skip to content

Commit 722abec

Browse files
pcloudsgitster
authored andcommitted
worktree add: sanitize worktree names
Worktree names are based on $(basename $GIT_WORK_TREE). They aren't significant until 3a3b9d8 (refs: new ref types to make per-worktree refs visible to all worktrees - 2018-10-21), where worktree name could be part of a refname and must follow refname rules. Update 'worktree add' code to remove special characters to follow these rules. In the future the user will be able to specify the worktree name by themselves if they're not happy with this dumb character substitution. Reported-by: Konstantin Kharlamov <[email protected]> Helped-by: Jeff King <[email protected]> Signed-off-by: Nguyễn Thái Ngọc Duy <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 7d0c1f4 commit 722abec

File tree

4 files changed

+104
-20
lines changed

4 files changed

+104
-20
lines changed

builtin/worktree.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ static int add_worktree(const char *path, const char *refname,
275275
struct strbuf symref = STRBUF_INIT;
276276
struct commit *commit = NULL;
277277
int is_branch = 0;
278+
struct strbuf sb_name = STRBUF_INIT;
278279

279280
validate_worktree_add(path, opts);
280281

@@ -290,7 +291,13 @@ static int add_worktree(const char *path, const char *refname,
290291
die(_("invalid reference: %s"), refname);
291292

292293
name = worktree_basename(path, &len);
293-
git_path_buf(&sb_repo, "worktrees/%.*s", (int)(path + len - name), name);
294+
strbuf_add(&sb, name, path + len - name);
295+
sanitize_refname_component(sb.buf, &sb_name);
296+
if (!sb_name.len)
297+
BUG("How come '%s' becomes empty after sanitization?", sb.buf);
298+
strbuf_reset(&sb);
299+
name = sb_name.buf;
300+
git_path_buf(&sb_repo, "worktrees/%s", name);
294301
len = sb_repo.len;
295302
if (safe_create_leading_directories_const(sb_repo.buf))
296303
die_errno(_("could not create leading directories of '%s'"),
@@ -415,6 +422,7 @@ static int add_worktree(const char *path, const char *refname,
415422
strbuf_release(&symref);
416423
strbuf_release(&sb_repo);
417424
strbuf_release(&sb_git);
425+
strbuf_release(&sb_name);
418426
return ret;
419427
}
420428

refs.c

Lines changed: 84 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -72,30 +72,57 @@ static unsigned char refname_disposition[256] = {
7272
* - it ends with ".lock", or
7373
* - it contains a "@{" portion
7474
*/
75-
static int check_refname_component(const char *refname, int *flags)
75+
static int check_refname_component(const char *refname, int *flags,
76+
struct strbuf *sanitized)
7677
{
7778
const char *cp;
7879
char last = '\0';
80+
size_t component_start;
81+
82+
if (sanitized)
83+
component_start = sanitized->len;
7984

8085
for (cp = refname; ; cp++) {
8186
int ch = *cp & 255;
8287
unsigned char disp = refname_disposition[ch];
88+
89+
if (sanitized && disp != 1)
90+
strbuf_addch(sanitized, ch);
91+
8392
switch (disp) {
8493
case 1:
8594
goto out;
8695
case 2:
87-
if (last == '.')
88-
return -1; /* Refname contains "..". */
96+
if (last == '.') { /* Refname contains "..". */
97+
if (sanitized)
98+
sanitized->len--; /* collapse ".." to single "." */
99+
else
100+
return -1;
101+
}
89102
break;
90103
case 3:
91-
if (last == '@')
92-
return -1; /* Refname contains "@{". */
104+
if (last == '@') { /* Refname contains "@{". */
105+
if (sanitized)
106+
sanitized->buf[sanitized->len-1] = '-';
107+
else
108+
return -1;
109+
}
93110
break;
94111
case 4:
95-
return -1;
112+
/* forbidden char */
113+
if (sanitized)
114+
sanitized->buf[sanitized->len-1] = '-';
115+
else
116+
return -1;
117+
break;
96118
case 5:
97-
if (!(*flags & REFNAME_REFSPEC_PATTERN))
98-
return -1; /* refspec can't be a pattern */
119+
if (!(*flags & REFNAME_REFSPEC_PATTERN)) {
120+
/* refspec can't be a pattern */
121+
if (sanitized)
122+
sanitized->buf[sanitized->len-1] = '-';
123+
else
124+
return -1;
125+
}
99126

100127
/*
101128
* Unset the pattern flag so that we only accept
@@ -109,26 +136,48 @@ static int check_refname_component(const char *refname, int *flags)
109136
out:
110137
if (cp == refname)
111138
return 0; /* Component has zero length. */
112-
if (refname[0] == '.')
113-
return -1; /* Component starts with '.'. */
139+
140+
if (refname[0] == '.') { /* Component starts with '.'. */
141+
if (sanitized)
142+
sanitized->buf[component_start] = '-';
143+
else
144+
return -1;
145+
}
114146
if (cp - refname >= LOCK_SUFFIX_LEN &&
115-
!memcmp(cp - LOCK_SUFFIX_LEN, LOCK_SUFFIX, LOCK_SUFFIX_LEN))
116-
return -1; /* Refname ends with ".lock". */
147+
!memcmp(cp - LOCK_SUFFIX_LEN, LOCK_SUFFIX, LOCK_SUFFIX_LEN)) {
148+
if (!sanitized)
149+
return -1;
150+
/* Refname ends with ".lock". */
151+
while (strbuf_strip_suffix(sanitized, LOCK_SUFFIX)) {
152+
/* try again in case we have .lock.lock */
153+
}
154+
}
117155
return cp - refname;
118156
}
119157

120-
int check_refname_format(const char *refname, int flags)
158+
static int check_or_sanitize_refname(const char *refname, int flags,
159+
struct strbuf *sanitized)
121160
{
122161
int component_len, component_count = 0;
123162

124-
if (!strcmp(refname, "@"))
163+
if (!strcmp(refname, "@")) {
125164
/* Refname is a single character '@'. */
126-
return -1;
165+
if (sanitized)
166+
strbuf_addch(sanitized, '-');
167+
else
168+
return -1;
169+
}
127170

128171
while (1) {
172+
if (sanitized && sanitized->len)
173+
strbuf_complete(sanitized, '/');
174+
129175
/* We are at the start of a path component. */
130-
component_len = check_refname_component(refname, &flags);
131-
if (component_len <= 0)
176+
component_len = check_refname_component(refname, &flags,
177+
sanitized);
178+
if (sanitized && component_len == 0)
179+
; /* OK, omit empty component */
180+
else if (component_len <= 0)
132181
return -1;
133182

134183
component_count++;
@@ -138,13 +187,29 @@ int check_refname_format(const char *refname, int flags)
138187
refname += component_len + 1;
139188
}
140189

141-
if (refname[component_len - 1] == '.')
142-
return -1; /* Refname ends with '.'. */
190+
if (refname[component_len - 1] == '.') {
191+
/* Refname ends with '.'. */
192+
if (sanitized)
193+
; /* omit ending dot */
194+
else
195+
return -1;
196+
}
143197
if (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2)
144198
return -1; /* Refname has only one component. */
145199
return 0;
146200
}
147201

202+
int check_refname_format(const char *refname, int flags)
203+
{
204+
return check_or_sanitize_refname(refname, flags, NULL);
205+
}
206+
207+
void sanitize_refname_component(const char *refname, struct strbuf *out)
208+
{
209+
if (check_or_sanitize_refname(refname, REFNAME_ALLOW_ONELEVEL, out))
210+
BUG("sanitizing refname '%s' check returned error", refname);
211+
}
212+
148213
int refname_is_safe(const char *refname)
149214
{
150215
const char *rest;

refs.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,12 @@ int for_each_reflog(each_ref_fn fn, void *cb_data);
460460
*/
461461
int check_refname_format(const char *refname, int flags);
462462

463+
/*
464+
* Apply the rules from check_refname_format, but mutate the result until it
465+
* is acceptable, and place the result in "out".
466+
*/
467+
void sanitize_refname_component(const char *refname, struct strbuf *out);
468+
463469
const char *prettify_refname(const char *refname);
464470

465471
char *shorten_unambiguous_ref(const char *refname, int strict);

t/t2400-worktree-add.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,4 +570,9 @@ test_expect_success '"add" an existing locked but missing worktree' '
570570
git worktree add --force --force --detach gnoo
571571
'
572572

573+
test_expect_success FUNNYNAMES 'sanitize generated worktree name' '
574+
git worktree add --detach ". weird*..?.lock.lock" &&
575+
test -d .git/worktrees/---weird-.-
576+
'
577+
573578
test_done

0 commit comments

Comments
 (0)