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.
8 #include "git2/errors.h"
17 int git_refspec__parse(git_refspec
*refspec
, const char *input
, bool is_fetch
)
19 // Ported from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/remote.c#L518-636
23 const char *lhs
, *rhs
;
26 assert(refspec
&& input
);
28 memset(refspec
, 0x0, sizeof(git_refspec
));
29 refspec
->push
= !is_fetch
;
37 rhs
= strrchr(lhs
, ':');
40 * Before going on, special case ":" (or "+:") as a refspec
43 if (!is_fetch
&& rhs
== lhs
&& rhs
[1] == '\0') {
44 refspec
->matching
= 1;
45 refspec
->string
= git__strdup(input
);
46 GITERR_CHECK_ALLOC(refspec
->string
);
47 refspec
->src
= git__strdup("");
48 GITERR_CHECK_ALLOC(refspec
->src
);
49 refspec
->dst
= git__strdup("");
50 GITERR_CHECK_ALLOC(refspec
->dst
);
55 size_t rlen
= strlen(++rhs
);
56 if (rlen
|| !is_fetch
) {
57 is_glob
= (1 <= rlen
&& strchr(rhs
, '*'));
58 refspec
->dst
= git__strndup(rhs
, rlen
);
62 llen
= (rhs
? (size_t)(rhs
- lhs
- 1) : strlen(lhs
));
63 if (1 <= llen
&& memchr(lhs
, '*', llen
)) {
64 if ((rhs
&& !is_glob
) || (!rhs
&& is_fetch
))
67 } else if (rhs
&& is_glob
)
70 refspec
->pattern
= is_glob
;
71 refspec
->src
= git__strndup(lhs
, llen
);
72 flags
= GIT_REF_FORMAT_ALLOW_ONELEVEL
| GIT_REF_FORMAT_REFSPEC_SHORTHAND
73 | (is_glob
? GIT_REF_FORMAT_REFSPEC_PATTERN
: 0);
78 * - empty is allowed; it means HEAD.
79 * - otherwise it must be a valid looking ref.
83 else if (!git_reference__is_valid_name(refspec
->src
, flags
))
87 * - missing is ok, and is same as empty.
88 * - empty is ok; it means not to store.
89 * - otherwise it must be a valid looking ref.
93 else if (!*refspec
->dst
)
95 else if (!git_reference__is_valid_name(refspec
->dst
, flags
))
100 * - empty is allowed; it means delete.
101 * - when wildcarded, it must be a valid looking ref.
102 * - otherwise, it must be an extended SHA-1, but
103 * there is no existing way to validate this.
108 if (!git_reference__is_valid_name(refspec
->src
, flags
))
112 ; /* anything goes, for now */
116 * - missing is allowed, but LHS then must be a
118 * - empty is not allowed.
119 * - otherwise it must be a valid looking ref.
122 if (!git_reference__is_valid_name(refspec
->src
, flags
))
124 } else if (!*refspec
->dst
) {
127 if (!git_reference__is_valid_name(refspec
->dst
, flags
))
131 /* if the RHS is empty, then it's a copy of the LHS */
133 refspec
->dst
= git__strdup(refspec
->src
);
134 GITERR_CHECK_ALLOC(refspec
->dst
);
138 refspec
->string
= git__strdup(input
);
139 GITERR_CHECK_ALLOC(refspec
->string
);
146 "'%s' is not a valid refspec.", input
);
147 git_refspec__free(refspec
);
151 void git_refspec__free(git_refspec
*refspec
)
156 git__free(refspec
->src
);
157 git__free(refspec
->dst
);
158 git__free(refspec
->string
);
160 memset(refspec
, 0x0, sizeof(git_refspec
));
163 const char *git_refspec_src(const git_refspec
*refspec
)
165 return refspec
== NULL
? NULL
: refspec
->src
;
168 const char *git_refspec_dst(const git_refspec
*refspec
)
170 return refspec
== NULL
? NULL
: refspec
->dst
;
173 const char *git_refspec_string(const git_refspec
*refspec
)
175 return refspec
== NULL
? NULL
: refspec
->string
;
178 int git_refspec_force(const git_refspec
*refspec
)
182 return refspec
->force
;
185 int git_refspec_src_matches(const git_refspec
*refspec
, const char *refname
)
187 if (refspec
== NULL
|| refspec
->src
== NULL
)
190 return (p_fnmatch(refspec
->src
, refname
, 0) == 0);
193 int git_refspec_dst_matches(const git_refspec
*refspec
, const char *refname
)
195 if (refspec
== NULL
|| refspec
->dst
== NULL
)
198 return (p_fnmatch(refspec
->dst
, refname
, 0) == 0);
201 static int refspec_transform(
202 git_buf
*out
, const char *from
, const char *to
, const char *name
)
204 const char *from_star
, *to_star
;
205 const char *name_slash
, *from_slash
;
206 size_t replacement_len
, star_offset
;
208 git_buf_sanitize(out
);
212 * There are two parts to each side of a refspec, the bit
213 * before the star and the bit after it. The star can be in
214 * the middle of the pattern, so we need to look at each bit
217 from_star
= strchr(from
, '*');
218 to_star
= strchr(to
, '*');
220 assert(from_star
&& to_star
);
222 /* star offset, both in 'from' and in 'name' */
223 star_offset
= from_star
- from
;
225 /* the first half is copied over */
226 git_buf_put(out
, to
, to_star
- to
);
228 /* then we copy over the replacement, from the star's offset to the next slash in 'name' */
229 name_slash
= strchr(name
+ star_offset
, '/');
231 name_slash
= strrchr(name
, '\0');
233 /* if there is no slash after the star in 'from', we want to copy everything over */
234 from_slash
= strchr(from
+ star_offset
, '/');
236 name_slash
= strrchr(name
, '\0');
238 replacement_len
= (name_slash
- name
) - star_offset
;
239 git_buf_put(out
, name
+ star_offset
, replacement_len
);
241 return git_buf_puts(out
, to_star
+ 1);
244 int git_refspec_transform(git_buf
*out
, const git_refspec
*spec
, const char *name
)
246 assert(out
&& spec
&& name
);
247 git_buf_sanitize(out
);
249 if (!git_refspec_src_matches(spec
, name
)) {
250 giterr_set(GITERR_INVALID
, "ref '%s' doesn't match the source", name
);
255 return git_buf_puts(out
, spec
->dst
);
257 return refspec_transform(out
, spec
->src
, spec
->dst
, name
);
260 int git_refspec_rtransform(git_buf
*out
, const git_refspec
*spec
, const char *name
)
262 assert(out
&& spec
&& name
);
263 git_buf_sanitize(out
);
265 if (!git_refspec_dst_matches(spec
, name
)) {
266 giterr_set(GITERR_INVALID
, "ref '%s' doesn't match the destination", name
);
271 return git_buf_puts(out
, spec
->src
);
273 return refspec_transform(out
, spec
->dst
, spec
->src
, name
);
276 int git_refspec__serialize(git_buf
*out
, const git_refspec
*refspec
)
279 git_buf_putc(out
, '+');
281 git_buf_printf(out
, "%s:%s",
282 refspec
->src
!= NULL
? refspec
->src
: "",
283 refspec
->dst
!= NULL
? refspec
->dst
: "");
285 return git_buf_oom(out
) == false;
288 int git_refspec_is_wildcard(const git_refspec
*spec
)
290 assert(spec
&& spec
->src
);
292 return (spec
->src
[strlen(spec
->src
) - 1] == '*');
295 git_direction
git_refspec_direction(const git_refspec
*spec
)
302 int git_refspec__dwim_one(git_vector
*out
, git_refspec
*spec
, git_vector
*refs
)
304 git_buf buf
= GIT_BUF_INIT
;
308 const char* formatters
[] = {
310 GIT_REFS_TAGS_DIR
"%s",
311 GIT_REFS_HEADS_DIR
"%s",
315 git_refspec
*cur
= git__calloc(1, sizeof(git_refspec
));
316 GITERR_CHECK_ALLOC(cur
);
318 cur
->force
= spec
->force
;
319 cur
->push
= spec
->push
;
320 cur
->pattern
= spec
->pattern
;
321 cur
->matching
= spec
->matching
;
322 cur
->string
= git__strdup(spec
->string
);
324 /* shorthand on the lhs */
325 if (git__prefixcmp(spec
->src
, GIT_REFS_DIR
)) {
326 for (j
= 0; formatters
[j
]; j
++) {
328 git_buf_printf(&buf
, formatters
[j
], spec
->src
);
329 GITERR_CHECK_ALLOC_BUF(&buf
);
331 key
.name
= (char *) git_buf_cstr(&buf
);
332 if (!git_vector_search(&pos
, refs
, &key
)) {
333 /* we found something to match the shorthand, set src to that */
334 cur
->src
= git_buf_detach(&buf
);
339 /* No shorthands found, copy over the name */
340 if (cur
->src
== NULL
&& spec
->src
!= NULL
) {
341 cur
->src
= git__strdup(spec
->src
);
342 GITERR_CHECK_ALLOC(cur
->src
);
345 if (spec
->dst
&& git__prefixcmp(spec
->dst
, GIT_REFS_DIR
)) {
346 /* if it starts with "remotes" then we just prepend "refs/" */
347 if (!git__prefixcmp(spec
->dst
, "remotes/")) {
348 git_buf_puts(&buf
, GIT_REFS_DIR
);
350 git_buf_puts(&buf
, GIT_REFS_HEADS_DIR
);
353 git_buf_puts(&buf
, spec
->dst
);
354 GITERR_CHECK_ALLOC_BUF(&buf
);
356 cur
->dst
= git_buf_detach(&buf
);
361 if (cur
->dst
== NULL
&& spec
->dst
!= NULL
) {
362 cur
->dst
= git__strdup(spec
->dst
);
363 GITERR_CHECK_ALLOC(cur
->dst
);
366 return git_vector_insert(out
, cur
);