]> git.proxmox.com Git - libgit2.git/blob - src/fetchhead.c
ea610f39a8f7768159d3307e31f64c05226b5a17
[libgit2.git] / src / fetchhead.c
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 "fetchhead.h"
9
10 #include "git2/types.h"
11 #include "git2/oid.h"
12
13 #include "buffer.h"
14 #include "futils.h"
15 #include "filebuf.h"
16 #include "refs.h"
17 #include "net.h"
18 #include "repository.h"
19
20 int git_fetchhead_ref_cmp(const void *a, const void *b)
21 {
22 const git_fetchhead_ref *one = (const git_fetchhead_ref *)a;
23 const git_fetchhead_ref *two = (const git_fetchhead_ref *)b;
24
25 if (one->is_merge && !two->is_merge)
26 return -1;
27 if (two->is_merge && !one->is_merge)
28 return 1;
29
30 if (one->ref_name && two->ref_name)
31 return strcmp(one->ref_name, two->ref_name);
32 else if (one->ref_name)
33 return -1;
34 else if (two->ref_name)
35 return 1;
36
37 return 0;
38 }
39
40 static char *sanitized_remote_url(const char *remote_url)
41 {
42 git_net_url url = GIT_NET_URL_INIT;
43 char *sanitized = NULL;
44 int error;
45
46 if (git_net_url_parse(&url, remote_url) == 0) {
47 git_buf buf = GIT_BUF_INIT;
48
49 git__free(url.username);
50 git__free(url.password);
51 url.username = url.password = NULL;
52
53 if ((error = git_net_url_fmt(&buf, &url)) < 0)
54 goto fallback;
55
56 sanitized = git_buf_detach(&buf);
57 }
58
59 fallback:
60 if (!sanitized)
61 sanitized = git__strdup(remote_url);
62
63 git_net_url_dispose(&url);
64 return sanitized;
65 }
66
67 int git_fetchhead_ref_create(
68 git_fetchhead_ref **out,
69 git_oid *oid,
70 unsigned int is_merge,
71 const char *ref_name,
72 const char *remote_url)
73 {
74 git_fetchhead_ref *fetchhead_ref;
75
76 assert(out && oid);
77
78 *out = NULL;
79
80 fetchhead_ref = git__malloc(sizeof(git_fetchhead_ref));
81 GIT_ERROR_CHECK_ALLOC(fetchhead_ref);
82
83 memset(fetchhead_ref, 0x0, sizeof(git_fetchhead_ref));
84
85 git_oid_cpy(&fetchhead_ref->oid, oid);
86 fetchhead_ref->is_merge = is_merge;
87
88 if (ref_name) {
89 fetchhead_ref->ref_name = git__strdup(ref_name);
90 GIT_ERROR_CHECK_ALLOC(fetchhead_ref->ref_name);
91 }
92
93 if (remote_url) {
94 fetchhead_ref->remote_url = sanitized_remote_url(remote_url);
95 GIT_ERROR_CHECK_ALLOC(fetchhead_ref->remote_url);
96 }
97
98 *out = fetchhead_ref;
99
100 return 0;
101 }
102
103 static int fetchhead_ref_write(
104 git_filebuf *file,
105 git_fetchhead_ref *fetchhead_ref)
106 {
107 char oid[GIT_OID_HEXSZ + 1];
108 const char *type, *name;
109 int head = 0;
110
111 assert(file && fetchhead_ref);
112
113 git_oid_fmt(oid, &fetchhead_ref->oid);
114 oid[GIT_OID_HEXSZ] = '\0';
115
116 if (git__prefixcmp(fetchhead_ref->ref_name, GIT_REFS_HEADS_DIR) == 0) {
117 type = "branch ";
118 name = fetchhead_ref->ref_name + strlen(GIT_REFS_HEADS_DIR);
119 } else if(git__prefixcmp(fetchhead_ref->ref_name,
120 GIT_REFS_TAGS_DIR) == 0) {
121 type = "tag ";
122 name = fetchhead_ref->ref_name + strlen(GIT_REFS_TAGS_DIR);
123 } else if (!git__strcmp(fetchhead_ref->ref_name, GIT_HEAD_FILE)) {
124 head = 1;
125 } else {
126 type = "";
127 name = fetchhead_ref->ref_name;
128 }
129
130 if (head)
131 return git_filebuf_printf(file, "%s\t\t%s\n", oid, fetchhead_ref->remote_url);
132
133 return git_filebuf_printf(file, "%s\t%s\t%s'%s' of %s\n",
134 oid,
135 (fetchhead_ref->is_merge) ? "" : "not-for-merge",
136 type,
137 name,
138 fetchhead_ref->remote_url);
139 }
140
141 int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs)
142 {
143 git_filebuf file = GIT_FILEBUF_INIT;
144 git_buf path = GIT_BUF_INIT;
145 unsigned int i;
146 git_fetchhead_ref *fetchhead_ref;
147
148 assert(repo && fetchhead_refs);
149
150 if (git_buf_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0)
151 return -1;
152
153 if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_APPEND, GIT_REFS_FILE_MODE) < 0) {
154 git_buf_dispose(&path);
155 return -1;
156 }
157
158 git_buf_dispose(&path);
159
160 git_vector_sort(fetchhead_refs);
161
162 git_vector_foreach(fetchhead_refs, i, fetchhead_ref)
163 fetchhead_ref_write(&file, fetchhead_ref);
164
165 return git_filebuf_commit(&file);
166 }
167
168 static int fetchhead_ref_parse(
169 git_oid *oid,
170 unsigned int *is_merge,
171 git_buf *ref_name,
172 const char **remote_url,
173 char *line,
174 size_t line_num)
175 {
176 char *oid_str, *is_merge_str, *desc, *name = NULL;
177 const char *type = NULL;
178 int error = 0;
179
180 *remote_url = NULL;
181
182 if (!*line) {
183 git_error_set(GIT_ERROR_FETCHHEAD,
184 "empty line in FETCH_HEAD line %"PRIuZ, line_num);
185 return -1;
186 }
187
188 /* Compat with old git clients that wrote FETCH_HEAD like a loose ref. */
189 if ((oid_str = git__strsep(&line, "\t")) == NULL) {
190 oid_str = line;
191 line += strlen(line);
192
193 *is_merge = 1;
194 }
195
196 if (strlen(oid_str) != GIT_OID_HEXSZ) {
197 git_error_set(GIT_ERROR_FETCHHEAD,
198 "invalid object ID in FETCH_HEAD line %"PRIuZ, line_num);
199 return -1;
200 }
201
202 if (git_oid_fromstr(oid, oid_str) < 0) {
203 const git_error *oid_err = git_error_last();
204 const char *err_msg = oid_err ? oid_err->message : "invalid object ID";
205
206 git_error_set(GIT_ERROR_FETCHHEAD, "%s in FETCH_HEAD line %"PRIuZ,
207 err_msg, line_num);
208 return -1;
209 }
210
211 /* Parse new data from newer git clients */
212 if (*line) {
213 if ((is_merge_str = git__strsep(&line, "\t")) == NULL) {
214 git_error_set(GIT_ERROR_FETCHHEAD,
215 "invalid description data in FETCH_HEAD line %"PRIuZ, line_num);
216 return -1;
217 }
218
219 if (*is_merge_str == '\0')
220 *is_merge = 1;
221 else if (strcmp(is_merge_str, "not-for-merge") == 0)
222 *is_merge = 0;
223 else {
224 git_error_set(GIT_ERROR_FETCHHEAD,
225 "invalid for-merge entry in FETCH_HEAD line %"PRIuZ, line_num);
226 return -1;
227 }
228
229 if ((desc = line) == NULL) {
230 git_error_set(GIT_ERROR_FETCHHEAD,
231 "invalid description in FETCH_HEAD line %"PRIuZ, line_num);
232 return -1;
233 }
234
235 if (git__prefixcmp(desc, "branch '") == 0) {
236 type = GIT_REFS_HEADS_DIR;
237 name = desc + 8;
238 } else if (git__prefixcmp(desc, "tag '") == 0) {
239 type = GIT_REFS_TAGS_DIR;
240 name = desc + 5;
241 } else if (git__prefixcmp(desc, "'") == 0)
242 name = desc + 1;
243
244 if (name) {
245 if ((desc = strstr(name, "' ")) == NULL ||
246 git__prefixcmp(desc, "' of ") != 0) {
247 git_error_set(GIT_ERROR_FETCHHEAD,
248 "invalid description in FETCH_HEAD line %"PRIuZ, line_num);
249 return -1;
250 }
251
252 *desc = '\0';
253 desc += 5;
254 }
255
256 *remote_url = desc;
257 }
258
259 git_buf_clear(ref_name);
260
261 if (type)
262 git_buf_join(ref_name, '/', type, name);
263 else if(name)
264 git_buf_puts(ref_name, name);
265
266 return error;
267 }
268
269 int git_repository_fetchhead_foreach(git_repository *repo,
270 git_repository_fetchhead_foreach_cb cb,
271 void *payload)
272 {
273 git_buf path = GIT_BUF_INIT, file = GIT_BUF_INIT, name = GIT_BUF_INIT;
274 const char *ref_name;
275 git_oid oid;
276 const char *remote_url;
277 unsigned int is_merge = 0;
278 char *buffer, *line;
279 size_t line_num = 0;
280 int error = 0;
281
282 assert(repo && cb);
283
284 if (git_buf_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0)
285 return -1;
286
287 if ((error = git_futils_readbuffer(&file, git_buf_cstr(&path))) < 0)
288 goto done;
289
290 buffer = file.ptr;
291
292 while ((line = git__strsep(&buffer, "\n")) != NULL) {
293 ++line_num;
294
295 if ((error = fetchhead_ref_parse(
296 &oid, &is_merge, &name, &remote_url, line, line_num)) < 0)
297 goto done;
298
299 if (git_buf_len(&name) > 0)
300 ref_name = git_buf_cstr(&name);
301 else
302 ref_name = NULL;
303
304 error = cb(ref_name, remote_url, &oid, is_merge, payload);
305 if (error) {
306 git_error_set_after_callback(error);
307 goto done;
308 }
309 }
310
311 if (*buffer) {
312 git_error_set(GIT_ERROR_FETCHHEAD, "no EOL at line %"PRIuZ, line_num+1);
313 error = -1;
314 goto done;
315 }
316
317 done:
318 git_buf_dispose(&file);
319 git_buf_dispose(&path);
320 git_buf_dispose(&name);
321
322 return error;
323 }
324
325 void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref)
326 {
327 if (fetchhead_ref == NULL)
328 return;
329
330 git__free(fetchhead_ref->remote_url);
331 git__free(fetchhead_ref->ref_name);
332 git__free(fetchhead_ref);
333 }
334