]> git.proxmox.com Git - libgit2.git/blob - src/fetchhead.c
New upstream version 1.4.3+dfsg.1
[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 "str.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_str buf = GIT_STR_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_str_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 GIT_ASSERT_ARG(out);
77 GIT_ASSERT_ARG(oid);
78
79 *out = NULL;
80
81 fetchhead_ref = git__malloc(sizeof(git_fetchhead_ref));
82 GIT_ERROR_CHECK_ALLOC(fetchhead_ref);
83
84 memset(fetchhead_ref, 0x0, sizeof(git_fetchhead_ref));
85
86 git_oid_cpy(&fetchhead_ref->oid, oid);
87 fetchhead_ref->is_merge = is_merge;
88
89 if (ref_name) {
90 fetchhead_ref->ref_name = git__strdup(ref_name);
91 GIT_ERROR_CHECK_ALLOC(fetchhead_ref->ref_name);
92 }
93
94 if (remote_url) {
95 fetchhead_ref->remote_url = sanitized_remote_url(remote_url);
96 GIT_ERROR_CHECK_ALLOC(fetchhead_ref->remote_url);
97 }
98
99 *out = fetchhead_ref;
100
101 return 0;
102 }
103
104 static int fetchhead_ref_write(
105 git_filebuf *file,
106 git_fetchhead_ref *fetchhead_ref)
107 {
108 char oid[GIT_OID_HEXSZ + 1];
109 const char *type, *name;
110 int head = 0;
111
112 GIT_ASSERT_ARG(file);
113 GIT_ASSERT_ARG(fetchhead_ref);
114
115 git_oid_fmt(oid, &fetchhead_ref->oid);
116 oid[GIT_OID_HEXSZ] = '\0';
117
118 if (git__prefixcmp(fetchhead_ref->ref_name, GIT_REFS_HEADS_DIR) == 0) {
119 type = "branch ";
120 name = fetchhead_ref->ref_name + strlen(GIT_REFS_HEADS_DIR);
121 } else if(git__prefixcmp(fetchhead_ref->ref_name,
122 GIT_REFS_TAGS_DIR) == 0) {
123 type = "tag ";
124 name = fetchhead_ref->ref_name + strlen(GIT_REFS_TAGS_DIR);
125 } else if (!git__strcmp(fetchhead_ref->ref_name, GIT_HEAD_FILE)) {
126 head = 1;
127 } else {
128 type = "";
129 name = fetchhead_ref->ref_name;
130 }
131
132 if (head)
133 return git_filebuf_printf(file, "%s\t\t%s\n", oid, fetchhead_ref->remote_url);
134
135 return git_filebuf_printf(file, "%s\t%s\t%s'%s' of %s\n",
136 oid,
137 (fetchhead_ref->is_merge) ? "" : "not-for-merge",
138 type,
139 name,
140 fetchhead_ref->remote_url);
141 }
142
143 int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs)
144 {
145 git_filebuf file = GIT_FILEBUF_INIT;
146 git_str path = GIT_STR_INIT;
147 unsigned int i;
148 git_fetchhead_ref *fetchhead_ref;
149
150 GIT_ASSERT_ARG(repo);
151 GIT_ASSERT_ARG(fetchhead_refs);
152
153 if (git_str_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0)
154 return -1;
155
156 if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_APPEND, GIT_REFS_FILE_MODE) < 0) {
157 git_str_dispose(&path);
158 return -1;
159 }
160
161 git_str_dispose(&path);
162
163 git_vector_sort(fetchhead_refs);
164
165 git_vector_foreach(fetchhead_refs, i, fetchhead_ref)
166 fetchhead_ref_write(&file, fetchhead_ref);
167
168 return git_filebuf_commit(&file);
169 }
170
171 static int fetchhead_ref_parse(
172 git_oid *oid,
173 unsigned int *is_merge,
174 git_str *ref_name,
175 const char **remote_url,
176 char *line,
177 size_t line_num)
178 {
179 char *oid_str, *is_merge_str, *desc, *name = NULL;
180 const char *type = NULL;
181 int error = 0;
182
183 *remote_url = NULL;
184
185 if (!*line) {
186 git_error_set(GIT_ERROR_FETCHHEAD,
187 "empty line in FETCH_HEAD line %"PRIuZ, line_num);
188 return -1;
189 }
190
191 /* Compat with old git clients that wrote FETCH_HEAD like a loose ref. */
192 if ((oid_str = git__strsep(&line, "\t")) == NULL) {
193 oid_str = line;
194 line += strlen(line);
195
196 *is_merge = 1;
197 }
198
199 if (strlen(oid_str) != GIT_OID_HEXSZ) {
200 git_error_set(GIT_ERROR_FETCHHEAD,
201 "invalid object ID in FETCH_HEAD line %"PRIuZ, line_num);
202 return -1;
203 }
204
205 if (git_oid_fromstr(oid, oid_str) < 0) {
206 const git_error *oid_err = git_error_last();
207 const char *err_msg = oid_err ? oid_err->message : "invalid object ID";
208
209 git_error_set(GIT_ERROR_FETCHHEAD, "%s in FETCH_HEAD line %"PRIuZ,
210 err_msg, line_num);
211 return -1;
212 }
213
214 /* Parse new data from newer git clients */
215 if (*line) {
216 if ((is_merge_str = git__strsep(&line, "\t")) == NULL) {
217 git_error_set(GIT_ERROR_FETCHHEAD,
218 "invalid description data in FETCH_HEAD line %"PRIuZ, line_num);
219 return -1;
220 }
221
222 if (*is_merge_str == '\0')
223 *is_merge = 1;
224 else if (strcmp(is_merge_str, "not-for-merge") == 0)
225 *is_merge = 0;
226 else {
227 git_error_set(GIT_ERROR_FETCHHEAD,
228 "invalid for-merge entry in FETCH_HEAD line %"PRIuZ, line_num);
229 return -1;
230 }
231
232 if ((desc = line) == NULL) {
233 git_error_set(GIT_ERROR_FETCHHEAD,
234 "invalid description in FETCH_HEAD line %"PRIuZ, line_num);
235 return -1;
236 }
237
238 if (git__prefixcmp(desc, "branch '") == 0) {
239 type = GIT_REFS_HEADS_DIR;
240 name = desc + 8;
241 } else if (git__prefixcmp(desc, "tag '") == 0) {
242 type = GIT_REFS_TAGS_DIR;
243 name = desc + 5;
244 } else if (git__prefixcmp(desc, "'") == 0)
245 name = desc + 1;
246
247 if (name) {
248 if ((desc = strstr(name, "' ")) == NULL ||
249 git__prefixcmp(desc, "' of ") != 0) {
250 git_error_set(GIT_ERROR_FETCHHEAD,
251 "invalid description in FETCH_HEAD line %"PRIuZ, line_num);
252 return -1;
253 }
254
255 *desc = '\0';
256 desc += 5;
257 }
258
259 *remote_url = desc;
260 }
261
262 git_str_clear(ref_name);
263
264 if (type)
265 git_str_join(ref_name, '/', type, name);
266 else if(name)
267 git_str_puts(ref_name, name);
268
269 return error;
270 }
271
272 int git_repository_fetchhead_foreach(git_repository *repo,
273 git_repository_fetchhead_foreach_cb cb,
274 void *payload)
275 {
276 git_str path = GIT_STR_INIT, file = GIT_STR_INIT, name = GIT_STR_INIT;
277 const char *ref_name;
278 git_oid oid;
279 const char *remote_url;
280 unsigned int is_merge = 0;
281 char *buffer, *line;
282 size_t line_num = 0;
283 int error = 0;
284
285 GIT_ASSERT_ARG(repo);
286 GIT_ASSERT_ARG(cb);
287
288 if (git_str_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0)
289 return -1;
290
291 if ((error = git_futils_readbuffer(&file, git_str_cstr(&path))) < 0)
292 goto done;
293
294 buffer = file.ptr;
295
296 while ((line = git__strsep(&buffer, "\n")) != NULL) {
297 ++line_num;
298
299 if ((error = fetchhead_ref_parse(
300 &oid, &is_merge, &name, &remote_url, line, line_num)) < 0)
301 goto done;
302
303 if (git_str_len(&name) > 0)
304 ref_name = git_str_cstr(&name);
305 else
306 ref_name = NULL;
307
308 error = cb(ref_name, remote_url, &oid, is_merge, payload);
309 if (error) {
310 git_error_set_after_callback(error);
311 goto done;
312 }
313 }
314
315 if (*buffer) {
316 git_error_set(GIT_ERROR_FETCHHEAD, "no EOL at line %"PRIuZ, line_num+1);
317 error = -1;
318 goto done;
319 }
320
321 done:
322 git_str_dispose(&file);
323 git_str_dispose(&path);
324 git_str_dispose(&name);
325
326 return error;
327 }
328
329 void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref)
330 {
331 if (fetchhead_ref == NULL)
332 return;
333
334 git__free(fetchhead_ref->remote_url);
335 git__free(fetchhead_ref->ref_name);
336 git__free(fetchhead_ref);
337 }
338