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 
git_fetchhead_ref_cmp(const void * a,const void * b)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 
sanitized_remote_url(const char * remote_url)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 
git_fetchhead_ref_create(git_fetchhead_ref ** out,git_oid * oid,unsigned int is_merge,const char * ref_name,const char * remote_url)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 
fetchhead_ref_write(git_filebuf * file,git_fetchhead_ref * fetchhead_ref)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 
git_fetchhead_write(git_repository * repo,git_vector * fetchhead_refs)143 int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs)
144 {
145 	git_filebuf file = GIT_FILEBUF_INIT;
146 	git_buf path = GIT_BUF_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_buf_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_buf_dispose(&path);
158 		return -1;
159 	}
160 
161 	git_buf_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 
fetchhead_ref_parse(git_oid * oid,unsigned int * is_merge,git_buf * ref_name,const char ** remote_url,char * line,size_t line_num)171 static int fetchhead_ref_parse(
172 	git_oid *oid,
173 	unsigned int *is_merge,
174 	git_buf *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_buf_clear(ref_name);
263 
264 	if (type)
265 		git_buf_join(ref_name, '/', type, name);
266 	else if(name)
267 		git_buf_puts(ref_name, name);
268 
269 	return error;
270 }
271 
git_repository_fetchhead_foreach(git_repository * repo,git_repository_fetchhead_foreach_cb cb,void * payload)272 int git_repository_fetchhead_foreach(git_repository *repo,
273 	git_repository_fetchhead_foreach_cb cb,
274 	void *payload)
275 {
276 	git_buf path = GIT_BUF_INIT, file = GIT_BUF_INIT, name = GIT_BUF_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_buf_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0)
289 		return -1;
290 
291 	if ((error = git_futils_readbuffer(&file, git_buf_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_buf_len(&name) > 0)
304 			ref_name = git_buf_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_buf_dispose(&file);
323 	git_buf_dispose(&path);
324 	git_buf_dispose(&name);
325 
326 	return error;
327 }
328 
git_fetchhead_ref_free(git_fetchhead_ref * fetchhead_ref)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 
339