]>
Commit | Line | Data |
---|---|---|
9c82357b | 1 | /* |
359fc2d2 | 2 | * Copyright (C) the libgit2 contributors. All rights reserved. |
9c82357b | 3 | * |
bb742ede VM |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
9c82357b CMN |
6 | */ |
7 | ||
eae0bfdc PP |
8 | #include "refspec.h" |
9 | ||
e579e0f7 | 10 | #include "buf.h" |
0adfa20a | 11 | #include "refs.h" |
22a2d3d5 | 12 | #include "util.h" |
af613ecd | 13 | #include "vector.h" |
22a2d3d5 | 14 | #include "wildmatch.h" |
0adfa20a | 15 | |
16 | int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch) | |
17 | { | |
ac3d33df | 18 | /* Ported from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/remote.c#L518-636 */ |
0adfa20a | 19 | |
20 | size_t llen; | |
21 | int is_glob = 0; | |
22 | const char *lhs, *rhs; | |
c25aa7cd PP |
23 | int valid = 0; |
24 | unsigned int flags; | |
0adfa20a | 25 | |
c25aa7cd PP |
26 | GIT_ASSERT_ARG(refspec); |
27 | GIT_ASSERT_ARG(input); | |
0adfa20a | 28 | |
29 | memset(refspec, 0x0, sizeof(git_refspec)); | |
4330ab26 | 30 | refspec->push = !is_fetch; |
0adfa20a | 31 | |
32 | lhs = input; | |
33 | if (*lhs == '+') { | |
34 | refspec->force = 1; | |
35 | lhs++; | |
36 | } | |
37 | ||
38 | rhs = strrchr(lhs, ':'); | |
39 | ||
40 | /* | |
41 | * Before going on, special case ":" (or "+:") as a refspec | |
42 | * for matching refs. | |
43 | */ | |
44 | if (!is_fetch && rhs == lhs && rhs[1] == '\0') { | |
45 | refspec->matching = 1; | |
7cd4ba1b | 46 | refspec->string = git__strdup(input); |
ac3d33df | 47 | GIT_ERROR_CHECK_ALLOC(refspec->string); |
7cd4ba1b | 48 | refspec->src = git__strdup(""); |
ac3d33df | 49 | GIT_ERROR_CHECK_ALLOC(refspec->src); |
7cd4ba1b | 50 | refspec->dst = git__strdup(""); |
ac3d33df | 51 | GIT_ERROR_CHECK_ALLOC(refspec->dst); |
0adfa20a | 52 | return 0; |
53 | } | |
54 | ||
55 | if (rhs) { | |
56 | size_t rlen = strlen(++rhs); | |
85addddf PS |
57 | if (rlen || !is_fetch) { |
58 | is_glob = (1 <= rlen && strchr(rhs, '*')); | |
59 | refspec->dst = git__strndup(rhs, rlen); | |
60 | } | |
0adfa20a | 61 | } |
62 | ||
63 | llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs)); | |
64 | if (1 <= llen && memchr(lhs, '*', llen)) { | |
65 | if ((rhs && !is_glob) || (!rhs && is_fetch)) | |
66 | goto invalid; | |
67 | is_glob = 1; | |
68 | } else if (rhs && is_glob) | |
69 | goto invalid; | |
70 | ||
71 | refspec->pattern = is_glob; | |
72 | refspec->src = git__strndup(lhs, llen); | |
ac3d33df JK |
73 | flags = GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL | |
74 | GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND | | |
75 | (is_glob ? GIT_REFERENCE_FORMAT_REFSPEC_PATTERN : 0); | |
0adfa20a | 76 | |
77 | if (is_fetch) { | |
78 | /* | |
c25aa7cd PP |
79 | * LHS |
80 | * - empty is allowed; it means HEAD. | |
81 | * - otherwise it must be a valid looking ref. | |
82 | */ | |
0adfa20a | 83 | if (!*refspec->src) |
84 | ; /* empty is ok */ | |
c25aa7cd PP |
85 | else if (git_reference__name_is_valid(&valid, refspec->src, flags) < 0) |
86 | goto on_error; | |
87 | else if (!valid) | |
0adfa20a | 88 | goto invalid; |
c25aa7cd | 89 | |
0adfa20a | 90 | /* |
c25aa7cd PP |
91 | * RHS |
92 | * - missing is ok, and is same as empty. | |
93 | * - empty is ok; it means not to store. | |
94 | * - otherwise it must be a valid looking ref. | |
95 | */ | |
0adfa20a | 96 | if (!refspec->dst) |
97 | ; /* ok */ | |
98 | else if (!*refspec->dst) | |
99 | ; /* ok */ | |
c25aa7cd PP |
100 | else if (git_reference__name_is_valid(&valid, refspec->dst, flags) < 0) |
101 | goto on_error; | |
102 | else if (!valid) | |
0adfa20a | 103 | goto invalid; |
104 | } else { | |
105 | /* | |
c25aa7cd PP |
106 | * LHS |
107 | * - empty is allowed; it means delete. | |
108 | * - when wildcarded, it must be a valid looking ref. | |
109 | * - otherwise, it must be an extended SHA-1, but | |
110 | * there is no existing way to validate this. | |
111 | */ | |
0adfa20a | 112 | if (!*refspec->src) |
113 | ; /* empty is ok */ | |
114 | else if (is_glob) { | |
c25aa7cd PP |
115 | if (git_reference__name_is_valid(&valid, refspec->src, flags) < 0) |
116 | goto on_error; | |
117 | else if (!valid) | |
0adfa20a | 118 | goto invalid; |
119 | } | |
120 | else { | |
121 | ; /* anything goes, for now */ | |
122 | } | |
c25aa7cd | 123 | |
0adfa20a | 124 | /* |
c25aa7cd PP |
125 | * RHS |
126 | * - missing is allowed, but LHS then must be a | |
127 | * valid looking ref. | |
128 | * - empty is not allowed. | |
129 | * - otherwise it must be a valid looking ref. | |
130 | */ | |
0adfa20a | 131 | if (!refspec->dst) { |
c25aa7cd PP |
132 | if (git_reference__name_is_valid(&valid, refspec->src, flags) < 0) |
133 | goto on_error; | |
134 | else if (!valid) | |
0adfa20a | 135 | goto invalid; |
136 | } else if (!*refspec->dst) { | |
137 | goto invalid; | |
138 | } else { | |
c25aa7cd PP |
139 | if (git_reference__name_is_valid(&valid, refspec->dst, flags) < 0) |
140 | goto on_error; | |
141 | else if (!valid) | |
0adfa20a | 142 | goto invalid; |
143 | } | |
aad638f3 CMN |
144 | |
145 | /* if the RHS is empty, then it's a copy of the LHS */ | |
146 | if (!refspec->dst) { | |
147 | refspec->dst = git__strdup(refspec->src); | |
ac3d33df | 148 | GIT_ERROR_CHECK_ALLOC(refspec->dst); |
aad638f3 | 149 | } |
0adfa20a | 150 | } |
151 | ||
1be680c4 | 152 | refspec->string = git__strdup(input); |
ac3d33df | 153 | GIT_ERROR_CHECK_ALLOC(refspec->string); |
1be680c4 | 154 | |
0adfa20a | 155 | return 0; |
156 | ||
c25aa7cd PP |
157 | invalid: |
158 | git_error_set(GIT_ERROR_INVALID, | |
159 | "'%s' is not a valid refspec.", input); | |
160 | git_refspec__dispose(refspec); | |
161 | return GIT_EINVALIDSPEC; | |
162 | ||
163 | on_error: | |
164 | git_refspec__dispose(refspec); | |
0adfa20a | 165 | return -1; |
166 | } | |
9c82357b | 167 | |
ac3d33df | 168 | void git_refspec__dispose(git_refspec *refspec) |
9c82357b | 169 | { |
a379e652 | 170 | if (refspec == NULL) |
171 | return; | |
172 | ||
3665ba8e CMN |
173 | git__free(refspec->src); |
174 | git__free(refspec->dst); | |
1be680c4 | 175 | git__free(refspec->string); |
64e6b5b0 JH |
176 | |
177 | memset(refspec, 0x0, sizeof(git_refspec)); | |
9c82357b CMN |
178 | } |
179 | ||
ac3d33df JK |
180 | int git_refspec_parse(git_refspec **out_refspec, const char *input, int is_fetch) |
181 | { | |
182 | git_refspec *refspec; | |
c25aa7cd PP |
183 | GIT_ASSERT_ARG(out_refspec); |
184 | GIT_ASSERT_ARG(input); | |
ac3d33df JK |
185 | |
186 | *out_refspec = NULL; | |
187 | ||
188 | refspec = git__malloc(sizeof(git_refspec)); | |
189 | GIT_ERROR_CHECK_ALLOC(refspec); | |
190 | ||
191 | if (git_refspec__parse(refspec, input, !!is_fetch) != 0) { | |
192 | git__free(refspec); | |
193 | return -1; | |
194 | } | |
195 | ||
196 | *out_refspec = refspec; | |
197 | return 0; | |
198 | } | |
199 | ||
200 | void git_refspec_free(git_refspec *refspec) | |
201 | { | |
202 | git_refspec__dispose(refspec); | |
203 | git__free(refspec); | |
204 | } | |
205 | ||
9c82357b CMN |
206 | const char *git_refspec_src(const git_refspec *refspec) |
207 | { | |
4a3b18a6 | 208 | return refspec == NULL ? NULL : refspec->src; |
9c82357b CMN |
209 | } |
210 | ||
211 | const char *git_refspec_dst(const git_refspec *refspec) | |
212 | { | |
4a3b18a6 | 213 | return refspec == NULL ? NULL : refspec->dst; |
9c82357b | 214 | } |
63f91e1c | 215 | |
1be680c4 CMN |
216 | const char *git_refspec_string(const git_refspec *refspec) |
217 | { | |
218 | return refspec == NULL ? NULL : refspec->string; | |
219 | } | |
220 | ||
d05e2c64 | 221 | int git_refspec_force(const git_refspec *refspec) |
222 | { | |
c25aa7cd | 223 | GIT_ASSERT_ARG(refspec); |
d05e2c64 | 224 | |
225 | return refspec->force; | |
226 | } | |
227 | ||
3fbcac89 | 228 | int git_refspec_src_matches(const git_refspec *refspec, const char *refname) |
63f91e1c | 229 | { |
3fbcac89 VM |
230 | if (refspec == NULL || refspec->src == NULL) |
231 | return false; | |
232 | ||
22a2d3d5 | 233 | return (wildmatch(refspec->src, refname, 0) == 0); |
63f91e1c | 234 | } |
92cb6aa9 | 235 | |
2e3e8c88 JM |
236 | int git_refspec_dst_matches(const git_refspec *refspec, const char *refname) |
237 | { | |
238 | if (refspec == NULL || refspec->dst == NULL) | |
239 | return false; | |
db4bb415 | 240 | |
22a2d3d5 | 241 | return (wildmatch(refspec->dst, refname, 0) == 0); |
2e3e8c88 JM |
242 | } |
243 | ||
55ededfd | 244 | static int refspec_transform( |
e579e0f7 | 245 | git_str *out, const char *from, const char *to, const char *name) |
97769280 | 246 | { |
f5287fa6 | 247 | const char *from_star, *to_star; |
f5287fa6 | 248 | size_t replacement_len, star_offset; |
97769280 | 249 | |
e579e0f7 | 250 | git_str_clear(out); |
bf522e08 | 251 | |
f5287fa6 CMN |
252 | /* |
253 | * There are two parts to each side of a refspec, the bit | |
254 | * before the star and the bit after it. The star can be in | |
255 | * the middle of the pattern, so we need to look at each bit | |
256 | * individually. | |
257 | */ | |
258 | from_star = strchr(from, '*'); | |
259 | to_star = strchr(to, '*'); | |
97769280 | 260 | |
c25aa7cd | 261 | GIT_ASSERT(from_star && to_star); |
f5287fa6 CMN |
262 | |
263 | /* star offset, both in 'from' and in 'name' */ | |
264 | star_offset = from_star - from; | |
265 | ||
266 | /* the first half is copied over */ | |
e579e0f7 | 267 | git_str_put(out, to, to_star - to); |
f5287fa6 | 268 | |
22a2d3d5 UG |
269 | /* |
270 | * Copy over the name, but exclude the trailing part in "from" starting | |
271 | * after the glob | |
272 | */ | |
273 | replacement_len = strlen(name + star_offset) - strlen(from_star + 1); | |
e579e0f7 | 274 | git_str_put(out, name + star_offset, replacement_len); |
cb8a7961 | 275 | |
e579e0f7 | 276 | return git_str_puts(out, to_star + 1); |
97769280 | 277 | } |
cb8a7961 | 278 | |
bf522e08 | 279 | int git_refspec_transform(git_buf *out, const git_refspec *spec, const char *name) |
3e012fca | 280 | { |
e579e0f7 MB |
281 | GIT_BUF_WRAP_PRIVATE(out, git_refspec__transform, spec, name); |
282 | } | |
c25aa7cd | 283 | |
e579e0f7 MB |
284 | int git_refspec__transform(git_str *out, const git_refspec *spec, const char *name) |
285 | { | |
c25aa7cd PP |
286 | GIT_ASSERT_ARG(out); |
287 | GIT_ASSERT_ARG(spec); | |
288 | GIT_ASSERT_ARG(name); | |
289 | ||
4e53c280 | 290 | if (!git_refspec_src_matches(spec, name)) { |
ac3d33df | 291 | git_error_set(GIT_ERROR_INVALID, "ref '%s' doesn't match the source", name); |
4e53c280 JG |
292 | return -1; |
293 | } | |
294 | ||
8f6073f6 | 295 | if (!spec->pattern) |
e579e0f7 | 296 | return git_str_puts(out, spec->dst ? spec->dst : ""); |
8f6073f6 | 297 | |
3e012fca | 298 | return refspec_transform(out, spec->src, spec->dst, name); |
299 | } | |
300 | ||
bf522e08 | 301 | int git_refspec_rtransform(git_buf *out, const git_refspec *spec, const char *name) |
3e012fca | 302 | { |
e579e0f7 MB |
303 | GIT_BUF_WRAP_PRIVATE(out, git_refspec__rtransform, spec, name); |
304 | } | |
c25aa7cd | 305 | |
e579e0f7 MB |
306 | int git_refspec__rtransform(git_str *out, const git_refspec *spec, const char *name) |
307 | { | |
c25aa7cd PP |
308 | GIT_ASSERT_ARG(out); |
309 | GIT_ASSERT_ARG(spec); | |
310 | GIT_ASSERT_ARG(name); | |
311 | ||
4e53c280 | 312 | if (!git_refspec_dst_matches(spec, name)) { |
ac3d33df | 313 | git_error_set(GIT_ERROR_INVALID, "ref '%s' doesn't match the destination", name); |
4e53c280 JG |
314 | return -1; |
315 | } | |
316 | ||
8f6073f6 | 317 | if (!spec->pattern) |
e579e0f7 | 318 | return git_str_puts(out, spec->src); |
8f6073f6 | 319 | |
3e012fca | 320 | return refspec_transform(out, spec->dst, spec->src, name); |
321 | } | |
322 | ||
e579e0f7 | 323 | int git_refspec__serialize(git_str *out, const git_refspec *refspec) |
fb39b3a5 | 324 | { |
325 | if (refspec->force) | |
e579e0f7 | 326 | git_str_putc(out, '+'); |
fb39b3a5 | 327 | |
e579e0f7 | 328 | git_str_printf(out, "%s:%s", |
fb39b3a5 | 329 | refspec->src != NULL ? refspec->src : "", |
330 | refspec->dst != NULL ? refspec->dst : ""); | |
331 | ||
e579e0f7 | 332 | return git_str_oom(out) == false; |
fb39b3a5 | 333 | } |
b0f6e45d ET |
334 | |
335 | int git_refspec_is_wildcard(const git_refspec *spec) | |
336 | { | |
c25aa7cd PP |
337 | GIT_ASSERT_ARG(spec); |
338 | GIT_ASSERT_ARG(spec->src); | |
b0f6e45d ET |
339 | |
340 | return (spec->src[strlen(spec->src) - 1] == '*'); | |
341 | } | |
8d39f2a7 CMN |
342 | |
343 | git_direction git_refspec_direction(const git_refspec *spec) | |
344 | { | |
c25aa7cd | 345 | GIT_ASSERT_ARG(spec); |
8d39f2a7 CMN |
346 | |
347 | return spec->push; | |
348 | } | |
af613ecd CMN |
349 | |
350 | int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs) | |
351 | { | |
e579e0f7 | 352 | git_str buf = GIT_STR_INIT; |
af613ecd CMN |
353 | size_t j, pos; |
354 | git_remote_head key; | |
4b3ec53c | 355 | git_refspec *cur; |
af613ecd | 356 | |
c25aa7cd | 357 | const char *formatters[] = { |
af613ecd CMN |
358 | GIT_REFS_DIR "%s", |
359 | GIT_REFS_TAGS_DIR "%s", | |
360 | GIT_REFS_HEADS_DIR "%s", | |
361 | NULL | |
362 | }; | |
363 | ||
c25aa7cd PP |
364 | GIT_ASSERT_ARG(out); |
365 | GIT_ASSERT_ARG(spec); | |
366 | GIT_ASSERT_ARG(refs); | |
4b3ec53c XL |
367 | |
368 | cur = git__calloc(1, sizeof(git_refspec)); | |
ac3d33df | 369 | GIT_ERROR_CHECK_ALLOC(cur); |
af613ecd CMN |
370 | |
371 | cur->force = spec->force; | |
372 | cur->push = spec->push; | |
373 | cur->pattern = spec->pattern; | |
374 | cur->matching = spec->matching; | |
375 | cur->string = git__strdup(spec->string); | |
376 | ||
377 | /* shorthand on the lhs */ | |
378 | if (git__prefixcmp(spec->src, GIT_REFS_DIR)) { | |
379 | for (j = 0; formatters[j]; j++) { | |
e579e0f7 MB |
380 | git_str_clear(&buf); |
381 | git_str_printf(&buf, formatters[j], spec->src); | |
382 | GIT_ERROR_CHECK_ALLOC_STR(&buf); | |
af613ecd | 383 | |
e579e0f7 | 384 | key.name = (char *) git_str_cstr(&buf); |
af613ecd CMN |
385 | if (!git_vector_search(&pos, refs, &key)) { |
386 | /* we found something to match the shorthand, set src to that */ | |
e579e0f7 | 387 | cur->src = git_str_detach(&buf); |
af613ecd CMN |
388 | } |
389 | } | |
390 | } | |
391 | ||
392 | /* No shorthands found, copy over the name */ | |
393 | if (cur->src == NULL && spec->src != NULL) { | |
394 | cur->src = git__strdup(spec->src); | |
ac3d33df | 395 | GIT_ERROR_CHECK_ALLOC(cur->src); |
af613ecd CMN |
396 | } |
397 | ||
398 | if (spec->dst && git__prefixcmp(spec->dst, GIT_REFS_DIR)) { | |
399 | /* if it starts with "remotes" then we just prepend "refs/" */ | |
400 | if (!git__prefixcmp(spec->dst, "remotes/")) { | |
e579e0f7 | 401 | git_str_puts(&buf, GIT_REFS_DIR); |
af613ecd | 402 | } else { |
e579e0f7 | 403 | git_str_puts(&buf, GIT_REFS_HEADS_DIR); |
af613ecd CMN |
404 | } |
405 | ||
e579e0f7 MB |
406 | git_str_puts(&buf, spec->dst); |
407 | GIT_ERROR_CHECK_ALLOC_STR(&buf); | |
af613ecd | 408 | |
e579e0f7 | 409 | cur->dst = git_str_detach(&buf); |
af613ecd CMN |
410 | } |
411 | ||
e579e0f7 | 412 | git_str_dispose(&buf); |
af613ecd CMN |
413 | |
414 | if (cur->dst == NULL && spec->dst != NULL) { | |
415 | cur->dst = git__strdup(spec->dst); | |
ac3d33df | 416 | GIT_ERROR_CHECK_ALLOC(cur->dst); |
af613ecd CMN |
417 | } |
418 | ||
419 | return git_vector_insert(out, cur); | |
420 | } |