1 #include "clar_libgit2.h"
2 #include "posix.h"
3 #include "path.h"
4 #include "git2/sys/repository.h"
5 
cl_git_report_failure(int error,int expected,const char * file,const char * func,int line,const char * fncall)6 void cl_git_report_failure(
7 	int error, int expected, const char *file, const char *func, int line, const char *fncall)
8 {
9 	char msg[4096];
10 	const git_error *last = git_error_last();
11 
12 	if (expected)
13 		p_snprintf(msg, 4096, "error %d (expected %d) - %s",
14 			error, expected, last ? last->message : "<no message>");
15 	else if (error || last)
16 		p_snprintf(msg, 4096, "error %d - %s",
17 			error, last ? last->message : "<no message>");
18 	else
19 		p_snprintf(msg, 4096, "no error, expected non-zero return");
20 
21 	clar__assert(0, file, func, line, fncall, msg, 1);
22 }
23 
cl_git_mkfile(const char * filename,const char * content)24 void cl_git_mkfile(const char *filename, const char *content)
25 {
26 	int fd;
27 
28 	fd = p_creat(filename, 0666);
29 	cl_assert(fd != -1);
30 
31 	if (content) {
32 		cl_must_pass(p_write(fd, content, strlen(content)));
33 	} else {
34 		cl_must_pass(p_write(fd, filename, strlen(filename)));
35 		cl_must_pass(p_write(fd, "\n", 1));
36 	}
37 
38 	cl_must_pass(p_close(fd));
39 }
40 
cl_git_write2file(const char * path,const char * content,size_t content_len,int flags,unsigned int mode)41 void cl_git_write2file(
42 	const char *path, const char *content, size_t content_len,
43 	int flags, unsigned int mode)
44 {
45 	int fd;
46 	cl_assert(path && content);
47 	cl_assert((fd = p_open(path, flags, mode)) >= 0);
48 	if (!content_len)
49 		content_len = strlen(content);
50 	cl_must_pass(p_write(fd, content, content_len));
51 	cl_must_pass(p_close(fd));
52 }
53 
cl_git_append2file(const char * path,const char * content)54 void cl_git_append2file(const char *path, const char *content)
55 {
56 	cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_APPEND, 0644);
57 }
58 
cl_git_rewritefile(const char * path,const char * content)59 void cl_git_rewritefile(const char *path, const char *content)
60 {
61 	cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_TRUNC, 0644);
62 }
63 
cl_git_rmfile(const char * filename)64 void cl_git_rmfile(const char *filename)
65 {
66 	cl_must_pass(p_unlink(filename));
67 }
68 
cl_getenv(const char * name)69 char *cl_getenv(const char *name)
70 {
71 	git_buf out = GIT_BUF_INIT;
72 	int error = git__getenv(&out, name);
73 
74 	cl_assert(error >= 0 || error == GIT_ENOTFOUND);
75 
76 	if (error == GIT_ENOTFOUND)
77 		return NULL;
78 
79 	if (out.size == 0) {
80 		char *dup = git__strdup("");
81 		cl_assert(dup);
82 
83 		return dup;
84 	}
85 
86 	return git_buf_detach(&out);
87 }
88 
cl_is_env_set(const char * name)89 bool cl_is_env_set(const char *name)
90 {
91 	char *env = cl_getenv(name);
92 	bool result = (env != NULL);
93 	git__free(env);
94 	return result;
95 }
96 
97 #ifdef GIT_WIN32
98 
99 #include "win32/utf-conv.h"
100 
cl_setenv(const char * name,const char * value)101 int cl_setenv(const char *name, const char *value)
102 {
103 	wchar_t *wide_name, *wide_value = NULL;
104 
105 	cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0);
106 
107 	if (value) {
108 		cl_assert(git__utf8_to_16_alloc(&wide_value, value) >= 0);
109 		cl_assert(SetEnvironmentVariableW(wide_name, wide_value));
110 	} else {
111 		/* Windows XP returns 0 (failed) when passing NULL for lpValue when
112 		* lpName does not exist in the environment block. This behavior
113 		* seems to have changed in later versions. Don't check the return value
114 		* of SetEnvironmentVariable when passing NULL for lpValue. */
115 		SetEnvironmentVariableW(wide_name, NULL);
116 	}
117 
118 	git__free(wide_name);
119 	git__free(wide_value);
120 	return 0;
121 }
122 
123 /* This function performs retries on calls to MoveFile in order
124  * to provide enhanced reliability in the face of antivirus
125  * agents that may be scanning the source (or in the case that
126  * the source is a directory, a child of the source). */
cl_rename(const char * source,const char * dest)127 int cl_rename(const char *source, const char *dest)
128 {
129 	git_win32_path source_utf16;
130 	git_win32_path dest_utf16;
131 	unsigned retries = 1;
132 
133 	cl_assert(git_win32_path_from_utf8(source_utf16, source) >= 0);
134 	cl_assert(git_win32_path_from_utf8(dest_utf16, dest) >= 0);
135 
136 	while (!MoveFileW(source_utf16, dest_utf16)) {
137 		/* Only retry if the error is ERROR_ACCESS_DENIED;
138 		 * this may indicate that an antivirus agent is
139 		 * preventing the rename from source to target */
140 		if (retries > 5 ||
141 			ERROR_ACCESS_DENIED != GetLastError())
142 			return -1;
143 
144 		/* With 5 retries and a coefficient of 10ms, the maximum
145 		 * delay here is 550 ms */
146 		Sleep(10 * retries * retries);
147 		retries++;
148 	}
149 
150 	return 0;
151 }
152 
153 #else
154 
155 #include <stdlib.h>
156 
cl_setenv(const char * name,const char * value)157 int cl_setenv(const char *name, const char *value)
158 {
159 	return (value == NULL) ? unsetenv(name) : setenv(name, value, 1);
160 }
161 
cl_rename(const char * source,const char * dest)162 int cl_rename(const char *source, const char *dest)
163 {
164 	return p_rename(source, dest);
165 }
166 
167 #endif
168 
169 static const char *_cl_sandbox = NULL;
170 static git_repository *_cl_repo = NULL;
171 
cl_git_sandbox_init(const char * sandbox)172 git_repository *cl_git_sandbox_init(const char *sandbox)
173 {
174 	/* Get the name of the sandbox folder which will be created */
175 	const char *basename = cl_fixture_basename(sandbox);
176 
177 	/* Copy the whole sandbox folder from our fixtures to our test sandbox
178 	 * area.  After this it can be accessed with `./sandbox`
179 	 */
180 	cl_fixture_sandbox(sandbox);
181 	_cl_sandbox = sandbox;
182 
183 	cl_git_pass(p_chdir(basename));
184 
185 	/* If this is not a bare repo, then rename `sandbox/.gitted` to
186 	 * `sandbox/.git` which must be done since we cannot store a folder
187 	 * named `.git` inside the fixtures folder of our libgit2 repo.
188 	 */
189 	if (p_access(".gitted", F_OK) == 0)
190 		cl_git_pass(cl_rename(".gitted", ".git"));
191 
192 	/* If we have `gitattributes`, rename to `.gitattributes`.  This may
193 	 * be necessary if we don't want the attributes to be applied in the
194 	 * libgit2 repo, but just during testing.
195 	 */
196 	if (p_access("gitattributes", F_OK) == 0)
197 		cl_git_pass(cl_rename("gitattributes", ".gitattributes"));
198 
199 	/* As with `gitattributes`, we may need `gitignore` just for testing. */
200 	if (p_access("gitignore", F_OK) == 0)
201 		cl_git_pass(cl_rename("gitignore", ".gitignore"));
202 
203 	cl_git_pass(p_chdir(".."));
204 
205 	/* Now open the sandbox repository and make it available for tests */
206 	cl_git_pass(git_repository_open(&_cl_repo, basename));
207 
208 	/* Adjust configs after copying to new filesystem */
209 	cl_git_pass(git_repository_reinit_filesystem(_cl_repo, 0));
210 
211 	return _cl_repo;
212 }
213 
cl_git_sandbox_init_new(const char * sandbox)214 git_repository *cl_git_sandbox_init_new(const char *sandbox)
215 {
216 	cl_git_pass(git_repository_init(&_cl_repo, sandbox, false));
217 	_cl_sandbox = sandbox;
218 
219 	return _cl_repo;
220 }
221 
cl_git_sandbox_reopen(void)222 git_repository *cl_git_sandbox_reopen(void)
223 {
224 	if (_cl_repo) {
225 		git_repository_free(_cl_repo);
226 		_cl_repo = NULL;
227 
228 		cl_git_pass(git_repository_open(
229 			&_cl_repo, cl_fixture_basename(_cl_sandbox)));
230 	}
231 
232 	return _cl_repo;
233 }
234 
cl_git_sandbox_cleanup(void)235 void cl_git_sandbox_cleanup(void)
236 {
237 	if (_cl_repo) {
238 		git_repository_free(_cl_repo);
239 		_cl_repo = NULL;
240 	}
241 	if (_cl_sandbox) {
242 		cl_fixture_cleanup(_cl_sandbox);
243 		_cl_sandbox = NULL;
244 	}
245 }
246 
cl_toggle_filemode(const char * filename)247 bool cl_toggle_filemode(const char *filename)
248 {
249 	struct stat st1, st2;
250 
251 	cl_must_pass(p_stat(filename, &st1));
252 	cl_must_pass(p_chmod(filename, st1.st_mode ^ 0100));
253 	cl_must_pass(p_stat(filename, &st2));
254 
255 	return (st1.st_mode != st2.st_mode);
256 }
257 
cl_is_chmod_supported(void)258 bool cl_is_chmod_supported(void)
259 {
260 	static int _is_supported = -1;
261 
262 	if (_is_supported < 0) {
263 		cl_git_mkfile("filemode.t", "Test if filemode can be modified");
264 		_is_supported = cl_toggle_filemode("filemode.t");
265 		cl_must_pass(p_unlink("filemode.t"));
266 	}
267 
268 	return _is_supported;
269 }
270 
cl_git_fixture_url(const char * fixturename)271 const char* cl_git_fixture_url(const char *fixturename)
272 {
273 	return cl_git_path_url(cl_fixture(fixturename));
274 }
275 
cl_git_path_url(const char * path)276 const char* cl_git_path_url(const char *path)
277 {
278 	static char url[4096 + 1];
279 
280 	const char *in_buf;
281 	git_buf path_buf = GIT_BUF_INIT;
282 	git_buf url_buf = GIT_BUF_INIT;
283 
284 	cl_git_pass(git_path_prettify_dir(&path_buf, path, NULL));
285 	cl_git_pass(git_buf_puts(&url_buf, "file://"));
286 
287 #ifdef GIT_WIN32
288 	/*
289 	 * A FILE uri matches the following format: file://[host]/path
290 	 * where "host" can be empty and "path" is an absolute path to the resource.
291 	 *
292 	 * In this test, no hostname is used, but we have to ensure the leading triple slashes:
293 	 *
294 	 * *nix: file:///usr/home/...
295 	 * Windows: file:///C:/Users/...
296 	 */
297 	cl_git_pass(git_buf_putc(&url_buf, '/'));
298 #endif
299 
300 	in_buf = git_buf_cstr(&path_buf);
301 
302 	/*
303 	 * A very hacky Url encoding that only takes care of escaping the spaces
304 	 */
305 	while (*in_buf) {
306 		if (*in_buf == ' ')
307 			cl_git_pass(git_buf_puts(&url_buf, "%20"));
308 		else
309 			cl_git_pass(git_buf_putc(&url_buf, *in_buf));
310 
311 		in_buf++;
312 	}
313 
314 	cl_assert(url_buf.size < sizeof(url) - 1);
315 
316 	strncpy(url, git_buf_cstr(&url_buf), sizeof(url) - 1);
317 	url[sizeof(url) - 1] = '\0';
318 	git_buf_dispose(&url_buf);
319 	git_buf_dispose(&path_buf);
320 	return url;
321 }
322 
cl_git_sandbox_path(int is_dir,...)323 const char *cl_git_sandbox_path(int is_dir, ...)
324 {
325 	const char *path = NULL;
326 	static char _temp[GIT_PATH_MAX];
327 	git_buf buf = GIT_BUF_INIT;
328 	va_list arg;
329 
330 	cl_git_pass(git_buf_sets(&buf, clar_sandbox_path()));
331 
332 	va_start(arg, is_dir);
333 
334 	while ((path = va_arg(arg, const char *)) != NULL) {
335 		cl_git_pass(git_buf_joinpath(&buf, buf.ptr, path));
336 	}
337 	va_end(arg);
338 
339 	cl_git_pass(git_path_prettify(&buf, buf.ptr, NULL));
340 	if (is_dir)
341 		git_path_to_dir(&buf);
342 
343 	/* make sure we won't truncate */
344 	cl_assert(git_buf_len(&buf) < sizeof(_temp));
345 	git_buf_copy_cstr(_temp, sizeof(_temp), &buf);
346 
347 	git_buf_dispose(&buf);
348 
349 	return _temp;
350 }
351 
352 typedef struct {
353 	const char *filename;
354 	size_t filename_len;
355 } remove_data;
356 
remove_placeholders_recurs(void * _data,git_buf * path)357 static int remove_placeholders_recurs(void *_data, git_buf *path)
358 {
359 	remove_data *data = (remove_data *)_data;
360 	size_t pathlen;
361 
362 	if (git_path_isdir(path->ptr) == true)
363 		return git_path_direach(path, 0, remove_placeholders_recurs, data);
364 
365 	pathlen = path->size;
366 
367 	if (pathlen < data->filename_len)
368 		return 0;
369 
370 	/* if path ends in '/'+filename (or equals filename) */
371 	if (!strcmp(data->filename, path->ptr + pathlen - data->filename_len) &&
372 		(pathlen == data->filename_len ||
373 		 path->ptr[pathlen - data->filename_len - 1] == '/'))
374 		return p_unlink(path->ptr);
375 
376 	return 0;
377 }
378 
cl_git_remove_placeholders(const char * directory_path,const char * filename)379 int cl_git_remove_placeholders(const char *directory_path, const char *filename)
380 {
381 	int error;
382 	remove_data data;
383 	git_buf buffer = GIT_BUF_INIT;
384 
385 	if (git_path_isdir(directory_path) == false)
386 		return -1;
387 
388 	if (git_buf_sets(&buffer, directory_path) < 0)
389 		return -1;
390 
391 	data.filename = filename;
392 	data.filename_len = strlen(filename);
393 
394 	error = remove_placeholders_recurs(&data, &buffer);
395 
396 	git_buf_dispose(&buffer);
397 
398 	return error;
399 }
400 
401 #define CL_COMMIT_NAME "Libgit2 Tester"
402 #define CL_COMMIT_EMAIL "libgit2-test@github.com"
403 #define CL_COMMIT_MSG "Test commit of tree "
404 
cl_repo_commit_from_index(git_oid * out,git_repository * repo,git_signature * sig,git_time_t time,const char * msg)405 void cl_repo_commit_from_index(
406 	git_oid *out,
407 	git_repository *repo,
408 	git_signature *sig,
409 	git_time_t time,
410 	const char *msg)
411 {
412 	git_index *index;
413 	git_oid commit_id, tree_id;
414 	git_object *parent = NULL;
415 	git_reference *ref = NULL;
416 	git_tree *tree = NULL;
417 	char buf[128];
418 	int free_sig = (sig == NULL);
419 
420 	/* it is fine if looking up HEAD fails - we make this the first commit */
421 	git_revparse_ext(&parent, &ref, repo, "HEAD");
422 
423 	/* write the index content as a tree */
424 	cl_git_pass(git_repository_index(&index, repo));
425 	cl_git_pass(git_index_write_tree(&tree_id, index));
426 	cl_git_pass(git_index_write(index));
427 	git_index_free(index);
428 
429 	cl_git_pass(git_tree_lookup(&tree, repo, &tree_id));
430 
431 	if (sig)
432 		cl_assert(sig->name && sig->email);
433 	else if (!time)
434 		cl_git_pass(git_signature_now(&sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL));
435 	else
436 		cl_git_pass(git_signature_new(
437 			&sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL, time, 0));
438 
439 	if (!msg) {
440 		strcpy(buf, CL_COMMIT_MSG);
441 		git_oid_tostr(buf + strlen(CL_COMMIT_MSG),
442 			sizeof(buf) - strlen(CL_COMMIT_MSG), &tree_id);
443 		msg = buf;
444 	}
445 
446 	cl_git_pass(git_commit_create_v(
447 		&commit_id, repo, ref ? git_reference_name(ref) : "HEAD",
448 		sig, sig, NULL, msg, tree, parent ? 1 : 0, parent));
449 
450 	if (out)
451 		git_oid_cpy(out, &commit_id);
452 
453 	git_object_free(parent);
454 	git_reference_free(ref);
455 	if (free_sig)
456 		git_signature_free(sig);
457 	git_tree_free(tree);
458 }
459 
cl_repo_set_bool(git_repository * repo,const char * cfg,int value)460 void cl_repo_set_bool(git_repository *repo, const char *cfg, int value)
461 {
462 	git_config *config;
463 	cl_git_pass(git_repository_config(&config, repo));
464 	cl_git_pass(git_config_set_bool(config, cfg, value != 0));
465 	git_config_free(config);
466 }
467 
cl_repo_get_bool(git_repository * repo,const char * cfg)468 int cl_repo_get_bool(git_repository *repo, const char *cfg)
469 {
470 	int val = 0;
471 	git_config *config;
472 	cl_git_pass(git_repository_config(&config, repo));
473 	if (git_config_get_bool(&val, config, cfg) < 0)
474 		git_error_clear();
475 	git_config_free(config);
476 	return val;
477 }
478 
cl_repo_set_string(git_repository * repo,const char * cfg,const char * value)479 void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value)
480 {
481 	git_config *config;
482 	cl_git_pass(git_repository_config(&config, repo));
483 	cl_git_pass(git_config_set_string(config, cfg, value));
484 	git_config_free(config);
485 }
486 
487 /* this is essentially the code from git__unescape modified slightly */
strip_cr_from_buf(char * start,size_t len)488 static size_t strip_cr_from_buf(char *start, size_t len)
489 {
490 	char *scan, *trail, *end = start + len;
491 
492 	for (scan = trail = start; scan < end; trail++, scan++) {
493 		while (*scan == '\r')
494 			scan++; /* skip '\r' */
495 
496 		if (trail != scan)
497 			*trail = *scan;
498 	}
499 
500 	*trail = '\0';
501 
502 	return (trail - start);
503 }
504 
clar__assert_equal_file(const char * expected_data,size_t expected_bytes,int ignore_cr,const char * path,const char * file,const char * func,int line)505 void clar__assert_equal_file(
506 	const char *expected_data,
507 	size_t expected_bytes,
508 	int ignore_cr,
509 	const char *path,
510 	const char *file,
511 	const char *func,
512 	int line)
513 {
514 	char buf[4000];
515 	ssize_t bytes, total_bytes = 0;
516 	int fd = p_open(path, O_RDONLY | O_BINARY);
517 	cl_assert(fd >= 0);
518 
519 	if (expected_data && !expected_bytes)
520 		expected_bytes = strlen(expected_data);
521 
522 	while ((bytes = p_read(fd, buf, sizeof(buf))) != 0) {
523 		clar__assert(
524 			bytes > 0, file, func, line, "error reading from file", path, 1);
525 
526 		if (ignore_cr)
527 			bytes = strip_cr_from_buf(buf, bytes);
528 
529 		if (memcmp(expected_data, buf, bytes) != 0) {
530 			int pos;
531 			for (pos = 0; pos < bytes && expected_data[pos] == buf[pos]; ++pos)
532 				/* find differing byte offset */;
533 			p_snprintf(
534 				buf, sizeof(buf), "file content mismatch at byte %"PRIdZ,
535 				(ssize_t)(total_bytes + pos));
536 			p_close(fd);
537 			clar__fail(file, func, line, path, buf, 1);
538 		}
539 
540 		expected_data += bytes;
541 		total_bytes   += bytes;
542 	}
543 
544 	p_close(fd);
545 
546 	clar__assert(!bytes, file, func, line, "error reading from file", path, 1);
547 	clar__assert_equal(file, func, line, "mismatched file length", 1, "%"PRIuZ,
548 		(size_t)expected_bytes, (size_t)total_bytes);
549 }
550 
551 static char *_cl_restore_home = NULL;
552 
cl_fake_home_cleanup(void * payload)553 void cl_fake_home_cleanup(void *payload)
554 {
555 	char *restore = _cl_restore_home;
556 	_cl_restore_home = NULL;
557 
558 	GIT_UNUSED(payload);
559 
560 	if (restore) {
561 		cl_git_pass(git_libgit2_opts(
562 			GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, restore));
563 		git__free(restore);
564 	}
565 }
566 
cl_fake_home(void)567 void cl_fake_home(void)
568 {
569 	git_buf path = GIT_BUF_INIT;
570 
571 	cl_git_pass(git_libgit2_opts(
572 		GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &path));
573 
574 	_cl_restore_home = git_buf_detach(&path);
575 	cl_set_cleanup(cl_fake_home_cleanup, NULL);
576 
577 	if (!git_path_exists("home"))
578 		cl_must_pass(p_mkdir("home", 0777));
579 	cl_git_pass(git_path_prettify(&path, "home", NULL));
580 	cl_git_pass(git_libgit2_opts(
581 		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
582 	git_buf_dispose(&path);
583 }
584 
cl_sandbox_set_search_path_defaults(void)585 void cl_sandbox_set_search_path_defaults(void)
586 {
587 	git_buf path = GIT_BUF_INIT;
588 
589 	git_buf_joinpath(&path, clar_sandbox_path(), "__config");
590 
591 	if (!git_path_exists(path.ptr))
592 		cl_must_pass(p_mkdir(path.ptr, 0777));
593 
594 	git_libgit2_opts(
595 		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr);
596 	git_libgit2_opts(
597 		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr);
598 	git_libgit2_opts(
599 		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr);
600 	git_libgit2_opts(
601 		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_PROGRAMDATA, path.ptr);
602 
603 	git_buf_dispose(&path);
604 }
605 
606 #ifdef GIT_WIN32
cl_sandbox_supports_8dot3(void)607 bool cl_sandbox_supports_8dot3(void)
608 {
609 	git_buf longpath = GIT_BUF_INIT;
610 	char *shortname;
611 	bool supported;
612 
613 	cl_git_pass(
614 		git_buf_joinpath(&longpath, clar_sandbox_path(), "longer_than_8dot3"));
615 
616 	cl_git_write2file(longpath.ptr, "", 0, O_RDWR|O_CREAT, 0666);
617 	shortname = git_win32_path_8dot3_name(longpath.ptr);
618 
619 	supported = (shortname != NULL);
620 
621 	git__free(shortname);
622 	git_buf_dispose(&longpath);
623 
624 	return supported;
625 }
626 #endif
627 
628