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 	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 
fetchhead_ref_write(git_filebuf * file,git_fetchhead_ref * fetchhead_ref)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 
git_fetchhead_write(git_repository * repo,git_vector * fetchhead_refs)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 
fetchhead_ref_parse(git_oid * oid,unsigned int * is_merge,git_buf * ref_name,const char ** remote_url,char * line,size_t line_num)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 
git_repository_fetchhead_foreach(git_repository * repo,git_repository_fetchhead_foreach_cb cb,void * payload)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 
git_fetchhead_ref_free(git_fetchhead_ref * fetchhead_ref)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 
335