]> git.proxmox.com Git - libgit2.git/blame - src/refspec.c
Teach remote branch to return its remote
[libgit2.git] / src / refspec.c
CommitLineData
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
92cb6aa9
CMN
8#include "git2/errors.h"
9
9c82357b
CMN
10#include "common.h"
11#include "refspec.h"
63f91e1c 12#include "util.h"
3fbcac89 13#include "posix.h"
0adfa20a 14#include "refs.h"
15
16int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
17{
18 // Ported from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/remote.c#L518-636
19
20 size_t llen;
21 int is_glob = 0;
22 const char *lhs, *rhs;
23 int flags;
24
25 assert(refspec && input);
26
27 memset(refspec, 0x0, sizeof(git_refspec));
28
29 lhs = input;
30 if (*lhs == '+') {
31 refspec->force = 1;
32 lhs++;
33 }
34
35 rhs = strrchr(lhs, ':');
36
37 /*
38 * Before going on, special case ":" (or "+:") as a refspec
39 * for matching refs.
40 */
41 if (!is_fetch && rhs == lhs && rhs[1] == '\0') {
42 refspec->matching = 1;
43 return 0;
44 }
45
46 if (rhs) {
47 size_t rlen = strlen(++rhs);
48 is_glob = (1 <= rlen && strchr(rhs, '*'));
49 refspec->dst = git__strndup(rhs, rlen);
50 }
51
52 llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs));
53 if (1 <= llen && memchr(lhs, '*', llen)) {
54 if ((rhs && !is_glob) || (!rhs && is_fetch))
55 goto invalid;
56 is_glob = 1;
57 } else if (rhs && is_glob)
58 goto invalid;
59
60 refspec->pattern = is_glob;
61 refspec->src = git__strndup(lhs, llen);
62 flags = GIT_REF_FORMAT_ALLOW_ONELEVEL
63 | (is_glob ? GIT_REF_FORMAT_REFSPEC_PATTERN : 0);
64
65 if (is_fetch) {
66 /*
67 * LHS
68 * - empty is allowed; it means HEAD.
69 * - otherwise it must be a valid looking ref.
70 */
71 if (!*refspec->src)
72 ; /* empty is ok */
73 else if (!git_reference__is_valid_name(refspec->src, flags))
74 goto invalid;
75 /*
76 * RHS
77 * - missing is ok, and is same as empty.
78 * - empty is ok; it means not to store.
79 * - otherwise it must be a valid looking ref.
80 */
81 if (!refspec->dst)
82 ; /* ok */
83 else if (!*refspec->dst)
84 ; /* ok */
85 else if (!git_reference__is_valid_name(refspec->dst, flags))
86 goto invalid;
87 } else {
88 /*
89 * LHS
90 * - empty is allowed; it means delete.
91 * - when wildcarded, it must be a valid looking ref.
92 * - otherwise, it must be an extended SHA-1, but
93 * there is no existing way to validate this.
94 */
95 if (!*refspec->src)
96 ; /* empty is ok */
97 else if (is_glob) {
98 if (!git_reference__is_valid_name(refspec->src, flags))
99 goto invalid;
100 }
101 else {
102 ; /* anything goes, for now */
103 }
104 /*
105 * RHS
106 * - missing is allowed, but LHS then must be a
107 * valid looking ref.
108 * - empty is not allowed.
109 * - otherwise it must be a valid looking ref.
110 */
111 if (!refspec->dst) {
112 if (!git_reference__is_valid_name(refspec->src, flags))
113 goto invalid;
114 } else if (!*refspec->dst) {
115 goto invalid;
116 } else {
117 if (!git_reference__is_valid_name(refspec->dst, flags))
118 goto invalid;
119 }
120 }
121
122 return 0;
123
124 invalid:
125 return -1;
126}
9c82357b 127
3665ba8e 128void git_refspec__free(git_refspec *refspec)
9c82357b 129{
a379e652 130 if (refspec == NULL)
131 return;
132
3665ba8e
CMN
133 git__free(refspec->src);
134 git__free(refspec->dst);
9c82357b
CMN
135}
136
137const char *git_refspec_src(const git_refspec *refspec)
138{
4a3b18a6 139 return refspec == NULL ? NULL : refspec->src;
9c82357b
CMN
140}
141
142const char *git_refspec_dst(const git_refspec *refspec)
143{
4a3b18a6 144 return refspec == NULL ? NULL : refspec->dst;
9c82357b 145}
63f91e1c 146
d05e2c64 147int git_refspec_force(const git_refspec *refspec)
148{
149 assert(refspec);
150
151 return refspec->force;
152}
153
3fbcac89 154int git_refspec_src_matches(const git_refspec *refspec, const char *refname)
63f91e1c 155{
3fbcac89
VM
156 if (refspec == NULL || refspec->src == NULL)
157 return false;
158
159 return (p_fnmatch(refspec->src, refname, 0) == 0);
63f91e1c 160}
92cb6aa9 161
2e3e8c88
JM
162int git_refspec_dst_matches(const git_refspec *refspec, const char *refname)
163{
164 if (refspec == NULL || refspec->dst == NULL)
165 return false;
166
167 return (p_fnmatch(refspec->dst, refname, 0) == 0);
168}
169
87d9869f 170int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name)
92cb6aa9
CMN
171{
172 size_t baselen, namelen;
173
174 baselen = strlen(spec->dst);
3aa351ea
CMN
175 if (outlen <= baselen) {
176 giterr_set(GITERR_INVALID, "Reference name too long");
904b67e6 177 return GIT_EBUFS;
3aa351ea 178 }
92cb6aa9
CMN
179
180 /*
181 * No '*' at the end means that it's mapped to one specific local
182 * branch, so no actual transformation is needed.
183 */
184 if (spec->dst[baselen - 1] != '*') {
185 memcpy(out, spec->dst, baselen + 1); /* include '\0' */
3aa351ea 186 return 0;
92cb6aa9
CMN
187 }
188
189 /* There's a '*' at the end, so remove its length */
190 baselen--;
191
192 /* skip the prefix, -1 is for the '*' */
193 name += strlen(spec->src) - 1;
194
195 namelen = strlen(name);
196
3aa351ea
CMN
197 if (outlen <= baselen + namelen) {
198 giterr_set(GITERR_INVALID, "Reference name too long");
904b67e6 199 return GIT_EBUFS;
3aa351ea 200 }
92cb6aa9
CMN
201
202 memcpy(out, spec->dst, baselen);
203 memcpy(out + baselen, name, namelen + 1);
204
3aa351ea 205 return 0;
92cb6aa9 206}
97769280 207
3e012fca 208static int refspec_transform(git_buf *out, const char *from, const char *to, const char *name)
97769280 209{
3e012fca 210 if (git_buf_sets(out, to) < 0)
3aa351ea 211 return -1;
97769280
RB
212
213 /*
3e012fca 214 * No '*' at the end means that it's mapped to one specific
97769280
RB
215 * branch, so no actual transformation is needed.
216 */
fa6420f7 217 if (git_buf_len(out) > 0 && out->ptr[git_buf_len(out) - 1] != '*')
3aa351ea 218 return 0;
97769280 219
fa6420f7 220 git_buf_truncate(out, git_buf_len(out) - 1); /* remove trailing '*' */
3e012fca 221 git_buf_puts(out, name + strlen(from) - 1);
97769280 222
cb8a7961 223 if (git_buf_oom(out))
3aa351ea 224 return -1;
cb8a7961
VM
225
226 return 0;
97769280 227}
cb8a7961 228
3e012fca 229int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name)
230{
231 return refspec_transform(out, spec->src, spec->dst, name);
232}
233
234int git_refspec_transform_l(git_buf *out, const git_refspec *spec, const char *name)
235{
236 return refspec_transform(out, spec->dst, spec->src, name);
237}
238
fb39b3a5 239int git_refspec__serialize(git_buf *out, const git_refspec *refspec)
240{
241 if (refspec->force)
242 git_buf_putc(out, '+');
243
244 git_buf_printf(out, "%s:%s",
245 refspec->src != NULL ? refspec->src : "",
246 refspec->dst != NULL ? refspec->dst : "");
247
248 return git_buf_oom(out) == false;
249}
b0f6e45d
ET
250
251int git_refspec_is_wildcard(const git_refspec *spec)
252{
253 assert(spec && spec->src);
254
255 return (spec->src[strlen(spec->src) - 1] == '*');
256}