refspec: factor out parsing a single refspec
[git] / refspec.c
1 #include "cache.h"
2 #include "refs.h"
3 #include "refspec.h"
4
5 static struct refspec_item s_tag_refspec = {
6         0,
7         1,
8         0,
9         0,
10         "refs/tags/*",
11         "refs/tags/*"
12 };
13
14 /* See TAG_REFSPEC for the string version */
15 const struct refspec_item *tag_refspec = &s_tag_refspec;
16
17 /*
18  * Parses the provided refspec 'refspec' and populates the refspec_item 'item'.
19  * Returns 1 if successful and 0 if the refspec is invalid.
20  */
21 static int parse_refspec(struct refspec_item *item, const char *refspec, int fetch)
22 {
23         size_t llen;
24         int is_glob;
25         const char *lhs, *rhs;
26         int flags;
27
28         is_glob = 0;
29
30         lhs = refspec;
31         if (*lhs == '+') {
32                 item->force = 1;
33                 lhs++;
34         }
35
36         rhs = strrchr(lhs, ':');
37
38         /*
39          * Before going on, special case ":" (or "+:") as a refspec
40          * for pushing matching refs.
41          */
42         if (!fetch && rhs == lhs && rhs[1] == '\0') {
43                 item->matching = 1;
44                 return 1;
45         }
46
47         if (rhs) {
48                 size_t rlen = strlen(++rhs);
49                 is_glob = (1 <= rlen && strchr(rhs, '*'));
50                 item->dst = xstrndup(rhs, rlen);
51         }
52
53         llen = (rhs ? (rhs - lhs - 1) : strlen(lhs));
54         if (1 <= llen && memchr(lhs, '*', llen)) {
55                 if ((rhs && !is_glob) || (!rhs && fetch))
56                         return 0;
57                 is_glob = 1;
58         } else if (rhs && is_glob) {
59                 return 0;
60         }
61
62         item->pattern = is_glob;
63         item->src = xstrndup(lhs, llen);
64         flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0);
65
66         if (fetch) {
67                 struct object_id unused;
68
69                 /* LHS */
70                 if (!*item->src)
71                         ; /* empty is ok; it means "HEAD" */
72                 else if (llen == GIT_SHA1_HEXSZ && !get_oid_hex(item->src, &unused))
73                         item->exact_sha1 = 1; /* ok */
74                 else if (!check_refname_format(item->src, flags))
75                         ; /* valid looking ref is ok */
76                 else
77                         return 0;
78                 /* RHS */
79                 if (!item->dst)
80                         ; /* missing is ok; it is the same as empty */
81                 else if (!*item->dst)
82                         ; /* empty is ok; it means "do not store" */
83                 else if (!check_refname_format(item->dst, flags))
84                         ; /* valid looking ref is ok */
85                 else
86                         return 0;
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 (!*item->src)
96                         ; /* empty is ok */
97                 else if (is_glob) {
98                         if (check_refname_format(item->src, flags))
99                                 return 0;
100                 }
101                 else
102                         ; /* anything goes, for now */
103                 /*
104                  * RHS
105                  * - missing is allowed, but LHS then must be a
106                  *   valid looking ref.
107                  * - empty is not allowed.
108                  * - otherwise it must be a valid looking ref.
109                  */
110                 if (!item->dst) {
111                         if (check_refname_format(item->src, flags))
112                                 return 0;
113                 } else if (!*item->dst) {
114                         return 0;
115                 } else {
116                         if (check_refname_format(item->dst, flags))
117                                 return 0;
118                 }
119         }
120
121         return 1;
122 }
123
124 static struct refspec_item *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch, int verify)
125 {
126         int i;
127         struct refspec_item *rs = xcalloc(nr_refspec, sizeof(*rs));
128
129         for (i = 0; i < nr_refspec; i++) {
130                 if (!parse_refspec(&rs[i], refspec[i], fetch))
131                         goto invalid;
132         }
133
134         return rs;
135
136  invalid:
137         if (verify) {
138                 /*
139                  * nr_refspec must be greater than zero and i must be valid
140                  * since it is only possible to reach this point from within
141                  * the for loop above.
142                  */
143                 free_refspec(i+1, rs);
144                 return NULL;
145         }
146         die("Invalid refspec '%s'", refspec[i]);
147 }
148
149 int valid_fetch_refspec(const char *fetch_refspec_str)
150 {
151         struct refspec_item *refspec;
152
153         refspec = parse_refspec_internal(1, &fetch_refspec_str, 1, 1);
154         free_refspec(1, refspec);
155         return !!refspec;
156 }
157
158 struct refspec_item *parse_fetch_refspec(int nr_refspec, const char **refspec)
159 {
160         return parse_refspec_internal(nr_refspec, refspec, 1, 0);
161 }
162
163 struct refspec_item *parse_push_refspec(int nr_refspec, const char **refspec)
164 {
165         return parse_refspec_internal(nr_refspec, refspec, 0, 0);
166 }
167
168 void free_refspec(int nr_refspec, struct refspec_item *refspec)
169 {
170         int i;
171
172         if (!refspec)
173                 return;
174
175         for (i = 0; i < nr_refspec; i++) {
176                 free(refspec[i].src);
177                 free(refspec[i].dst);
178         }
179         free(refspec);
180 }