2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
14 #include "wildmatch.h"
16 int git_refspec__parse(git_refspec
*refspec
, const char *input
, bool is_fetch
)
18 /* Ported from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/remote.c#L518-636 */
22 const char *lhs
, *rhs
;
26 GIT_ASSERT_ARG(refspec
);
27 GIT_ASSERT_ARG(input
);
29 memset(refspec
, 0x0, sizeof(git_refspec
));
30 refspec
->push
= !is_fetch
;
38 rhs
= strrchr(lhs
, ':');
41 * Before going on, special case ":" (or "+:") as a refspec
44 if (!is_fetch
&& rhs
== lhs
&& rhs
[1] == '\0') {
45 refspec
->matching
= 1;
46 refspec
->string
= git__strdup(input
);
47 GIT_ERROR_CHECK_ALLOC(refspec
->string
);
48 refspec
->src
= git__strdup("");
49 GIT_ERROR_CHECK_ALLOC(refspec
->src
);
50 refspec
->dst
= git__strdup("");
51 GIT_ERROR_CHECK_ALLOC(refspec
->dst
);
56 size_t rlen
= strlen(++rhs
);
57 if (rlen
|| !is_fetch
) {
58 is_glob
= (1 <= rlen
&& strchr(rhs
, '*'));
59 refspec
->dst
= git__strndup(rhs
, rlen
);
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
))
68 } else if (rhs
&& is_glob
)
71 refspec
->pattern
= is_glob
;
72 refspec
->src
= git__strndup(lhs
, llen
);
73 flags
= GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL
|
74 GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND
|
75 (is_glob
? GIT_REFERENCE_FORMAT_REFSPEC_PATTERN
: 0);
80 * - empty is allowed; it means HEAD.
81 * - otherwise it must be a valid looking ref.
85 else if (git_reference__name_is_valid(&valid
, refspec
->src
, flags
) < 0)
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.
98 else if (!*refspec
->dst
)
100 else if (git_reference__name_is_valid(&valid
, refspec
->dst
, flags
) < 0)
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.
115 if (git_reference__name_is_valid(&valid
, refspec
->src
, flags
) < 0)
121 ; /* anything goes, for now */
126 * - missing is allowed, but LHS then must be a
128 * - empty is not allowed.
129 * - otherwise it must be a valid looking ref.
132 if (git_reference__name_is_valid(&valid
, refspec
->src
, flags
) < 0)
136 } else if (!*refspec
->dst
) {
139 if (git_reference__name_is_valid(&valid
, refspec
->dst
, flags
) < 0)
145 /* if the RHS is empty, then it's a copy of the LHS */
147 refspec
->dst
= git__strdup(refspec
->src
);
148 GIT_ERROR_CHECK_ALLOC(refspec
->dst
);
152 refspec
->string
= git__strdup(input
);
153 GIT_ERROR_CHECK_ALLOC(refspec
->string
);
158 git_error_set(GIT_ERROR_INVALID
,
159 "'%s' is not a valid refspec.", input
);
160 git_refspec__dispose(refspec
);
161 return GIT_EINVALIDSPEC
;
164 git_refspec__dispose(refspec
);
168 void git_refspec__dispose(git_refspec
*refspec
)
173 git__free(refspec
->src
);
174 git__free(refspec
->dst
);
175 git__free(refspec
->string
);
177 memset(refspec
, 0x0, sizeof(git_refspec
));
180 int git_refspec_parse(git_refspec
**out_refspec
, const char *input
, int is_fetch
)
182 git_refspec
*refspec
;
183 GIT_ASSERT_ARG(out_refspec
);
184 GIT_ASSERT_ARG(input
);
188 refspec
= git__malloc(sizeof(git_refspec
));
189 GIT_ERROR_CHECK_ALLOC(refspec
);
191 if (git_refspec__parse(refspec
, input
, !!is_fetch
) != 0) {
196 *out_refspec
= refspec
;
200 void git_refspec_free(git_refspec
*refspec
)
202 git_refspec__dispose(refspec
);
206 const char *git_refspec_src(const git_refspec
*refspec
)
208 return refspec
== NULL
? NULL
: refspec
->src
;
211 const char *git_refspec_dst(const git_refspec
*refspec
)
213 return refspec
== NULL
? NULL
: refspec
->dst
;
216 const char *git_refspec_string(const git_refspec
*refspec
)
218 return refspec
== NULL
? NULL
: refspec
->string
;
221 int git_refspec_force(const git_refspec
*refspec
)
223 GIT_ASSERT_ARG(refspec
);
225 return refspec
->force
;
228 int git_refspec_src_matches(const git_refspec
*refspec
, const char *refname
)
230 if (refspec
== NULL
|| refspec
->src
== NULL
)
233 return (wildmatch(refspec
->src
, refname
, 0) == 0);
236 int git_refspec_dst_matches(const git_refspec
*refspec
, const char *refname
)
238 if (refspec
== NULL
|| refspec
->dst
== NULL
)
241 return (wildmatch(refspec
->dst
, refname
, 0) == 0);
244 static int refspec_transform(
245 git_str
*out
, const char *from
, const char *to
, const char *name
)
247 const char *from_star
, *to_star
;
248 size_t replacement_len
, star_offset
;
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
258 from_star
= strchr(from
, '*');
259 to_star
= strchr(to
, '*');
261 GIT_ASSERT(from_star
&& to_star
);
263 /* star offset, both in 'from' and in 'name' */
264 star_offset
= from_star
- from
;
266 /* the first half is copied over */
267 git_str_put(out
, to
, to_star
- to
);
270 * Copy over the name, but exclude the trailing part in "from" starting
273 replacement_len
= strlen(name
+ star_offset
) - strlen(from_star
+ 1);
274 git_str_put(out
, name
+ star_offset
, replacement_len
);
276 return git_str_puts(out
, to_star
+ 1);
279 int git_refspec_transform(git_buf
*out
, const git_refspec
*spec
, const char *name
)
281 GIT_BUF_WRAP_PRIVATE(out
, git_refspec__transform
, spec
, name
);
284 int git_refspec__transform(git_str
*out
, const git_refspec
*spec
, const char *name
)
287 GIT_ASSERT_ARG(spec
);
288 GIT_ASSERT_ARG(name
);
290 if (!git_refspec_src_matches(spec
, name
)) {
291 git_error_set(GIT_ERROR_INVALID
, "ref '%s' doesn't match the source", name
);
296 return git_str_puts(out
, spec
->dst
? spec
->dst
: "");
298 return refspec_transform(out
, spec
->src
, spec
->dst
, name
);
301 int git_refspec_rtransform(git_buf
*out
, const git_refspec
*spec
, const char *name
)
303 GIT_BUF_WRAP_PRIVATE(out
, git_refspec__rtransform
, spec
, name
);
306 int git_refspec__rtransform(git_str
*out
, const git_refspec
*spec
, const char *name
)
309 GIT_ASSERT_ARG(spec
);
310 GIT_ASSERT_ARG(name
);
312 if (!git_refspec_dst_matches(spec
, name
)) {
313 git_error_set(GIT_ERROR_INVALID
, "ref '%s' doesn't match the destination", name
);
318 return git_str_puts(out
, spec
->src
);
320 return refspec_transform(out
, spec
->dst
, spec
->src
, name
);
323 int git_refspec__serialize(git_str
*out
, const git_refspec
*refspec
)
326 git_str_putc(out
, '+');
328 git_str_printf(out
, "%s:%s",
329 refspec
->src
!= NULL
? refspec
->src
: "",
330 refspec
->dst
!= NULL
? refspec
->dst
: "");
332 return git_str_oom(out
) == false;
335 int git_refspec_is_wildcard(const git_refspec
*spec
)
337 GIT_ASSERT_ARG(spec
);
338 GIT_ASSERT_ARG(spec
->src
);
340 return (spec
->src
[strlen(spec
->src
) - 1] == '*');
343 git_direction
git_refspec_direction(const git_refspec
*spec
)
345 GIT_ASSERT_ARG(spec
);
350 int git_refspec__dwim_one(git_vector
*out
, git_refspec
*spec
, git_vector
*refs
)
352 git_str buf
= GIT_STR_INIT
;
357 const char *formatters
[] = {
359 GIT_REFS_TAGS_DIR
"%s",
360 GIT_REFS_HEADS_DIR
"%s",
365 GIT_ASSERT_ARG(spec
);
366 GIT_ASSERT_ARG(refs
);
368 cur
= git__calloc(1, sizeof(git_refspec
));
369 GIT_ERROR_CHECK_ALLOC(cur
);
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
);
377 /* shorthand on the lhs */
378 if (git__prefixcmp(spec
->src
, GIT_REFS_DIR
)) {
379 for (j
= 0; formatters
[j
]; j
++) {
381 git_str_printf(&buf
, formatters
[j
], spec
->src
);
382 GIT_ERROR_CHECK_ALLOC_STR(&buf
);
384 key
.name
= (char *) git_str_cstr(&buf
);
385 if (!git_vector_search(&pos
, refs
, &key
)) {
386 /* we found something to match the shorthand, set src to that */
387 cur
->src
= git_str_detach(&buf
);
392 /* No shorthands found, copy over the name */
393 if (cur
->src
== NULL
&& spec
->src
!= NULL
) {
394 cur
->src
= git__strdup(spec
->src
);
395 GIT_ERROR_CHECK_ALLOC(cur
->src
);
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/")) {
401 git_str_puts(&buf
, GIT_REFS_DIR
);
403 git_str_puts(&buf
, GIT_REFS_HEADS_DIR
);
406 git_str_puts(&buf
, spec
->dst
);
407 GIT_ERROR_CHECK_ALLOC_STR(&buf
);
409 cur
->dst
= git_str_detach(&buf
);
412 git_str_dispose(&buf
);
414 if (cur
->dst
== NULL
&& spec
->dst
!= NULL
) {
415 cur
->dst
= git__strdup(spec
->dst
);
416 GIT_ERROR_CHECK_ALLOC(cur
->dst
);
419 return git_vector_insert(out
, cur
);