refspec: store raw refspecs inside refspec_item

The refspec struct keeps two matched arrays: one for the refspec_item structs and one for the original raw refspec strings. The main reason for this is that there are other users of refspec_item that do not care about the raw strings. But it does make managing the refspec struct awkward, as we must keep the two arrays in sync. This has led to bugs in the past (both leaks and double-frees). Let's just store a copy of the raw refspec string directly in each refspec_item struct. This simplifies the handling at a small cost: 1. Direct callers of refspec_item_init() will now get an extra copy of the refspec string, even if they don't need it. This should be negligible, as the struct is already allocating two strings for the parsed src/dst values (and we tend to only do it sparingly anyway for things like the TAG_REFSPEC literal). 2. Users of refspec_appendf() will now generate a temporary string, copy it, and then free the result (versus handing off ownership of the temporary string). We could get around this by having a "nodup" variant of refspec_item_init(), but it doesn't seem worth the extra complexity for something that is not remotely a hot code path. Code which accesses refspec->raw now needs to look at refspec->item.raw. Other callers which just use refspec_item directly can remain the same. We'll free the allocated string in refspec_item_clear(), which they should be calling anyway to free src/dst. One subtle note: refspec_item_init() can return an error, in which case we'll still have set its "raw" field. But that is also true of the "src" and "dst" fields, so any caller which does not _clear() the failed item is already potentially leaking. In practice most code just calls die() on an error anyway, but you can see the exception in valid_fetch_refspec(), which does correctly call _clear() even on error. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Nov 12, 2024 at 03:39 UTC fe17a25905f701ce91505851eb2bb213bb39edbe
5 files changed +19 -31
builtin/fetch.c
+2 -6
@@ -454,14 +454,10 @@ static void filter_prefetch_refspec(struct refspec *rs)
454 ref_namespace[NAMESPACE_TAGS].ref))) {
455 int j;
456
457 - free(rs->items[i].src);
458 - free(rs->items[i].dst);
459 - free(rs->raw[i]);
457 + refspec_item_clear(&rs->items[i]);
458
461 - for (j = i + 1; j < rs->nr; j++) {
459 + for (j = i + 1; j < rs->nr; j++)
460 rs->items[j - 1] = rs->items[j];
463 - rs->raw[j - 1] = rs->raw[j];
464 - }
461 rs->nr--;
462 i--;
463 continue;
builtin/remote.c
+4 -4
@@ -377,7 +377,7 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat
377 for (i = 0; i < states->remote->fetch.nr; i++)
378 if (get_fetch_map(remote_refs, &states->remote->fetch.items[i], &tail, 1))
379 die(_("Could not get fetch map for refspec %s"),
380 - states->remote->fetch.raw[i]);
380 + states->remote->fetch.items[i].raw);
381
382 for (ref = fetch_map; ref; ref = ref->next) {
383 if (omit_name_by_refspec(ref->name, &states->remote->fetch))
@@ -634,11 +634,11 @@ static int migrate_file(struct remote *remote)
634 strbuf_reset(&buf);
635 strbuf_addf(&buf, "remote.%s.push", remote->name);
636 for (i = 0; i < remote->push.nr; i++)
637 - git_config_set_multivar(buf.buf, remote->push.raw[i], "^$", 0);
637 + git_config_set_multivar(buf.buf, remote->push.items[i].raw, "^$", 0);
638 strbuf_reset(&buf);
639 strbuf_addf(&buf, "remote.%s.fetch", remote->name);
640 for (i = 0; i < remote->fetch.nr; i++)
641 - git_config_set_multivar(buf.buf, remote->fetch.raw[i], "^$", 0);
641 + git_config_set_multivar(buf.buf, remote->fetch.items[i].raw, "^$", 0);
642 if (remote->origin == REMOTE_REMOTES)
643 unlink_or_warn(git_path("remotes/%s", remote->name));
644 else if (remote->origin == REMOTE_BRANCHES)
@@ -768,7 +768,7 @@ static int mv(int argc, const char **argv, const char *prefix)
768 char *ptr;
769
770 strbuf_reset(&buf2);
771 - strbuf_addstr(&buf2, oldremote->fetch.raw[i]);
771 + strbuf_addstr(&buf2, oldremote->fetch.items[i].raw);
772 ptr = strstr(buf2.buf, old_remote_context.buf);
773 if (ptr) {
774 refspec_updated = 1;
refspec.c
+9 -16
@@ -153,6 +153,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
153 int refspec_item_init(struct refspec_item *item, const char *refspec, int fetch)
154 {
155 memset(item, 0, sizeof(*item));
156 + item->raw = xstrdup(refspec);
157 return parse_refspec(item, refspec, fetch);
158 }
159
@@ -167,6 +168,7 @@ void refspec_item_clear(struct refspec_item *item)
168 {
169 FREE_AND_NULL(item->src);
170 FREE_AND_NULL(item->dst);
171 + FREE_AND_NULL(item->raw);
172 item->force = 0;
173 item->pattern = 0;
174 item->matching = 0;
@@ -179,7 +181,7 @@ void refspec_init(struct refspec *rs, int fetch)
181 rs->fetch = fetch;
182 }
183
182 -static void refspec_append_nodup(struct refspec *rs, char *refspec)
184 +void refspec_append(struct refspec *rs, const char *refspec)
185 {
186 struct refspec_item item;
187
@@ -188,24 +190,20 @@ static void refspec_append_nodup(struct refspec *rs, char *refspec)
190 ALLOC_GROW(rs->items, rs->nr + 1, rs->alloc);
191 rs->items[rs->nr] = item;
192
191 - ALLOC_GROW(rs->raw, rs->nr + 1, rs->raw_alloc);
192 - rs->raw[rs->nr] = refspec;
193 -
193 rs->nr++;
194 }
195
197 -void refspec_append(struct refspec *rs, const char *refspec)
198 -{
199 - refspec_append_nodup(rs, xstrdup(refspec));
200 -}
201 -
196 void refspec_appendf(struct refspec *rs, const char *fmt, ...)
197 {
198 va_list ap;
199 + char *buf;
200
201 va_start(ap, fmt);
207 - refspec_append_nodup(rs, xstrvfmt(fmt, ap));
202 + buf = xstrvfmt(fmt, ap);
203 va_end(ap);
204 +
205 + refspec_append(rs, buf);
206 + free(buf);
207 }
208
209 void refspec_appendn(struct refspec *rs, const char **refspecs, int nr)
@@ -219,18 +217,13 @@ void refspec_clear(struct refspec *rs)
217 {
218 int i;
219
222 - for (i = 0; i < rs->nr; i++) {
220 + for (i = 0; i < rs->nr; i++)
221 refspec_item_clear(&rs->items[i]);
224 - free(rs->raw[i]);
225 - }
222
223 FREE_AND_NULL(rs->items);
224 rs->alloc = 0;
225 rs->nr = 0;
226
231 - FREE_AND_NULL(rs->raw);
232 - rs->raw_alloc = 0;
233 -
227 rs->fetch = 0;
228 }
229
refspec.h
+2 -3
@@ -26,6 +26,8 @@ struct refspec_item {
26
27 char *src;
28 char *dst;
29 +
30 + char *raw;
31 };
32
33 #define REFSPEC_FETCH 1
@@ -43,9 +45,6 @@ struct refspec {
45 int alloc;
46 int nr;
47
46 - char **raw;
47 - int raw_alloc;
48 -
48 int fetch;
49 };
50
submodule.c
+2 -2
@@ -1175,7 +1175,7 @@ static int push_submodule(const char *path,
1175 int i;
1176 strvec_push(&cp.args, remote->name);
1177 for (i = 0; i < rs->nr; i++)
1178 - strvec_push(&cp.args, rs->raw[i]);
1178 + strvec_push(&cp.args, rs->items[i].raw);
1179 }
1180
1181 prepare_submodule_repo_env(&cp.env);
@@ -1210,7 +1210,7 @@ static void submodule_push_check(const char *path, const char *head,
1210 strvec_push(&cp.args, remote->name);
1211
1212 for (i = 0; i < rs->nr; i++)
1213 - strvec_push(&cp.args, rs->raw[i]);
1213 + strvec_push(&cp.args, rs->items[i].raw);
1214
1215 prepare_submodule_repo_env(&cp.env);
1216 cp.git_cmd = 1;