File Coverage

deps/libgit2/src/refspec.c
Criterion Covered Total %
statement 120 176 68.1
branch 76 180 42.2
condition n/a
subroutine n/a
pod n/a
total 196 356 55.0


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