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