1 #include "clar_libgit2.h"
4 #include "git2/sys/repository.h"
6 void cl_git_report_failure(
7 int error
, int expected
, const char *file
, const char *func
, int line
, const char *fncall
)
10 const git_error
*last
= git_error_last();
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>");
19 p_snprintf(msg
, 4096, "no error, expected non-zero return");
21 clar__assert(0, file
, func
, line
, fncall
, msg
, 1);
24 void cl_git_mkfile(const char *filename
, const char *content
)
28 fd
= p_creat(filename
, 0666);
32 cl_must_pass(p_write(fd
, content
, strlen(content
)));
34 cl_must_pass(p_write(fd
, filename
, strlen(filename
)));
35 cl_must_pass(p_write(fd
, "\n", 1));
38 cl_must_pass(p_close(fd
));
41 void cl_git_write2file(
42 const char *path
, const char *content
, size_t content_len
,
43 int flags
, unsigned int mode
)
46 cl_assert(path
&& content
);
47 cl_assert((fd
= p_open(path
, flags
, mode
)) >= 0);
49 content_len
= strlen(content
);
50 cl_must_pass(p_write(fd
, content
, content_len
));
51 cl_must_pass(p_close(fd
));
54 void cl_git_append2file(const char *path
, const char *content
)
56 cl_git_write2file(path
, content
, 0, O_WRONLY
| O_CREAT
| O_APPEND
, 0644);
59 void cl_git_rewritefile(const char *path
, const char *content
)
61 cl_git_write2file(path
, content
, 0, O_WRONLY
| O_CREAT
| O_TRUNC
, 0644);
64 void cl_git_rmfile(const char *filename
)
66 cl_must_pass(p_unlink(filename
));
69 char *cl_getenv(const char *name
)
71 git_buf out
= GIT_BUF_INIT
;
72 int error
= git__getenv(&out
, name
);
74 cl_assert(error
>= 0 || error
== GIT_ENOTFOUND
);
76 if (error
== GIT_ENOTFOUND
)
80 char *dup
= git__strdup("");
86 return git_buf_detach(&out
);
89 bool cl_is_env_set(const char *name
)
91 char *env
= cl_getenv(name
);
92 bool result
= (env
!= NULL
);
99 #include "win32/utf-conv.h"
101 int cl_setenv(const char *name
, const char *value
)
103 wchar_t *wide_name
, *wide_value
= NULL
;
105 cl_assert(git__utf8_to_16_alloc(&wide_name
, name
) >= 0);
108 cl_assert(git__utf8_to_16_alloc(&wide_value
, value
) >= 0);
109 cl_assert(SetEnvironmentVariableW(wide_name
, wide_value
));
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
);
118 git__free(wide_name
);
119 git__free(wide_value
);
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). */
127 int cl_rename(const char *source
, const char *dest
)
129 git_win32_path source_utf16
;
130 git_win32_path dest_utf16
;
131 unsigned retries
= 1;
133 cl_assert(git_win32_path_from_utf8(source_utf16
, source
) >= 0);
134 cl_assert(git_win32_path_from_utf8(dest_utf16
, dest
) >= 0);
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 */
141 ERROR_ACCESS_DENIED
!= GetLastError())
144 /* With 5 retries and a coefficient of 10ms, the maximum
145 * delay here is 550 ms */
146 Sleep(10 * retries
* retries
);
157 int cl_setenv(const char *name
, const char *value
)
159 return (value
== NULL
) ? unsetenv(name
) : setenv(name
, value
, 1);
162 int cl_rename(const char *source
, const char *dest
)
164 return p_rename(source
, dest
);
169 static const char *_cl_sandbox
= NULL
;
170 static git_repository
*_cl_repo
= NULL
;
172 git_repository
*cl_git_sandbox_init(const char *sandbox
)
174 /* Get the name of the sandbox folder which will be created */
175 const char *basename
= cl_fixture_basename(sandbox
);
177 /* Copy the whole sandbox folder from our fixtures to our test sandbox
178 * area. After this it can be accessed with `./sandbox`
180 cl_fixture_sandbox(sandbox
);
181 _cl_sandbox
= sandbox
;
183 cl_git_pass(p_chdir(basename
));
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.
189 if (p_access(".gitted", F_OK
) == 0)
190 cl_git_pass(cl_rename(".gitted", ".git"));
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.
196 if (p_access("gitattributes", F_OK
) == 0)
197 cl_git_pass(cl_rename("gitattributes", ".gitattributes"));
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"));
203 cl_git_pass(p_chdir(".."));
205 /* Now open the sandbox repository and make it available for tests */
206 cl_git_pass(git_repository_open(&_cl_repo
, basename
));
208 /* Adjust configs after copying to new filesystem */
209 cl_git_pass(git_repository_reinit_filesystem(_cl_repo
, 0));
214 git_repository
*cl_git_sandbox_init_new(const char *sandbox
)
216 cl_git_pass(git_repository_init(&_cl_repo
, sandbox
, false));
217 _cl_sandbox
= sandbox
;
222 git_repository
*cl_git_sandbox_reopen(void)
225 git_repository_free(_cl_repo
);
228 cl_git_pass(git_repository_open(
229 &_cl_repo
, cl_fixture_basename(_cl_sandbox
)));
235 void cl_git_sandbox_cleanup(void)
238 git_repository_free(_cl_repo
);
242 cl_fixture_cleanup(_cl_sandbox
);
247 bool cl_toggle_filemode(const char *filename
)
249 struct stat st1
, st2
;
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
));
255 return (st1
.st_mode
!= st2
.st_mode
);
258 bool cl_is_chmod_supported(void)
260 static int _is_supported
= -1;
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"));
268 return _is_supported
;
271 const char* cl_git_fixture_url(const char *fixturename
)
273 return cl_git_path_url(cl_fixture(fixturename
));
276 const char* cl_git_path_url(const char *path
)
278 static char url
[4096];
281 git_buf path_buf
= GIT_BUF_INIT
;
282 git_buf url_buf
= GIT_BUF_INIT
;
284 cl_git_pass(git_path_prettify_dir(&path_buf
, path
, NULL
));
285 cl_git_pass(git_buf_puts(&url_buf
, "file://"));
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.
292 * In this test, no hostname is used, but we have to ensure the leading triple slashes:
294 * *nix: file:///usr/home/...
295 * Windows: file:///C:/Users/...
297 cl_git_pass(git_buf_putc(&url_buf
, '/'));
300 in_buf
= git_buf_cstr(&path_buf
);
303 * A very hacky Url encoding that only takes care of escaping the spaces
307 cl_git_pass(git_buf_puts(&url_buf
, "%20"));
309 cl_git_pass(git_buf_putc(&url_buf
, *in_buf
));
314 cl_assert(url_buf
.size
< 4096);
316 strncpy(url
, git_buf_cstr(&url_buf
), 4096);
317 git_buf_dispose(&url_buf
);
318 git_buf_dispose(&path_buf
);
322 const char *cl_git_sandbox_path(int is_dir
, ...)
324 const char *path
= NULL
;
325 static char _temp
[GIT_PATH_MAX
];
326 git_buf buf
= GIT_BUF_INIT
;
329 cl_git_pass(git_buf_sets(&buf
, clar_sandbox_path()));
331 va_start(arg
, is_dir
);
333 while ((path
= va_arg(arg
, const char *)) != NULL
) {
334 cl_git_pass(git_buf_joinpath(&buf
, buf
.ptr
, path
));
338 cl_git_pass(git_path_prettify(&buf
, buf
.ptr
, NULL
));
340 git_path_to_dir(&buf
);
342 /* make sure we won't truncate */
343 cl_assert(git_buf_len(&buf
) < sizeof(_temp
));
344 git_buf_copy_cstr(_temp
, sizeof(_temp
), &buf
);
346 git_buf_dispose(&buf
);
352 const char *filename
;
356 static int remove_placeholders_recurs(void *_data
, git_buf
*path
)
358 remove_data
*data
= (remove_data
*)_data
;
361 if (git_path_isdir(path
->ptr
) == true)
362 return git_path_direach(path
, 0, remove_placeholders_recurs
, data
);
364 pathlen
= path
->size
;
366 if (pathlen
< data
->filename_len
)
369 /* if path ends in '/'+filename (or equals filename) */
370 if (!strcmp(data
->filename
, path
->ptr
+ pathlen
- data
->filename_len
) &&
371 (pathlen
== data
->filename_len
||
372 path
->ptr
[pathlen
- data
->filename_len
- 1] == '/'))
373 return p_unlink(path
->ptr
);
378 int cl_git_remove_placeholders(const char *directory_path
, const char *filename
)
382 git_buf buffer
= GIT_BUF_INIT
;
384 if (git_path_isdir(directory_path
) == false)
387 if (git_buf_sets(&buffer
, directory_path
) < 0)
390 data
.filename
= filename
;
391 data
.filename_len
= strlen(filename
);
393 error
= remove_placeholders_recurs(&data
, &buffer
);
395 git_buf_dispose(&buffer
);
400 #define CL_COMMIT_NAME "Libgit2 Tester"
401 #define CL_COMMIT_EMAIL "libgit2-test@github.com"
402 #define CL_COMMIT_MSG "Test commit of tree "
404 void cl_repo_commit_from_index(
406 git_repository
*repo
,
412 git_oid commit_id
, tree_id
;
413 git_object
*parent
= NULL
;
414 git_reference
*ref
= NULL
;
415 git_tree
*tree
= NULL
;
417 int free_sig
= (sig
== NULL
);
419 /* it is fine if looking up HEAD fails - we make this the first commit */
420 git_revparse_ext(&parent
, &ref
, repo
, "HEAD");
422 /* write the index content as a tree */
423 cl_git_pass(git_repository_index(&index
, repo
));
424 cl_git_pass(git_index_write_tree(&tree_id
, index
));
425 cl_git_pass(git_index_write(index
));
426 git_index_free(index
);
428 cl_git_pass(git_tree_lookup(&tree
, repo
, &tree_id
));
431 cl_assert(sig
->name
&& sig
->email
);
433 cl_git_pass(git_signature_now(&sig
, CL_COMMIT_NAME
, CL_COMMIT_EMAIL
));
435 cl_git_pass(git_signature_new(
436 &sig
, CL_COMMIT_NAME
, CL_COMMIT_EMAIL
, time
, 0));
439 strcpy(buf
, CL_COMMIT_MSG
);
440 git_oid_tostr(buf
+ strlen(CL_COMMIT_MSG
),
441 sizeof(buf
) - strlen(CL_COMMIT_MSG
), &tree_id
);
445 cl_git_pass(git_commit_create_v(
446 &commit_id
, repo
, ref
? git_reference_name(ref
) : "HEAD",
447 sig
, sig
, NULL
, msg
, tree
, parent
? 1 : 0, parent
));
450 git_oid_cpy(out
, &commit_id
);
452 git_object_free(parent
);
453 git_reference_free(ref
);
455 git_signature_free(sig
);
459 void cl_repo_set_bool(git_repository
*repo
, const char *cfg
, int value
)
462 cl_git_pass(git_repository_config(&config
, repo
));
463 cl_git_pass(git_config_set_bool(config
, cfg
, value
!= 0));
464 git_config_free(config
);
467 int cl_repo_get_bool(git_repository
*repo
, const char *cfg
)
471 cl_git_pass(git_repository_config(&config
, repo
));
472 if (git_config_get_bool(&val
, config
, cfg
) < 0)
474 git_config_free(config
);
478 void cl_repo_set_string(git_repository
*repo
, const char *cfg
, const char *value
)
481 cl_git_pass(git_repository_config(&config
, repo
));
482 cl_git_pass(git_config_set_string(config
, cfg
, value
));
483 git_config_free(config
);
486 /* this is essentially the code from git__unescape modified slightly */
487 static size_t strip_cr_from_buf(char *start
, size_t len
)
489 char *scan
, *trail
, *end
= start
+ len
;
491 for (scan
= trail
= start
; scan
< end
; trail
++, scan
++) {
492 while (*scan
== '\r')
493 scan
++; /* skip '\r' */
501 return (trail
- start
);
504 void clar__assert_equal_file(
505 const char *expected_data
,
506 size_t expected_bytes
,
514 ssize_t bytes
, total_bytes
= 0;
515 int fd
= p_open(path
, O_RDONLY
| O_BINARY
);
518 if (expected_data
&& !expected_bytes
)
519 expected_bytes
= strlen(expected_data
);
521 while ((bytes
= p_read(fd
, buf
, sizeof(buf
))) != 0) {
523 bytes
> 0, file
, func
, line
, "error reading from file", path
, 1);
526 bytes
= strip_cr_from_buf(buf
, bytes
);
528 if (memcmp(expected_data
, buf
, bytes
) != 0) {
530 for (pos
= 0; pos
< bytes
&& expected_data
[pos
] == buf
[pos
]; ++pos
)
531 /* find differing byte offset */;
533 buf
, sizeof(buf
), "file content mismatch at byte %"PRIdZ
,
534 (ssize_t
)(total_bytes
+ pos
));
536 clar__fail(file
, func
, line
, path
, buf
, 1);
539 expected_data
+= bytes
;
540 total_bytes
+= bytes
;
545 clar__assert(!bytes
, file
, func
, line
, "error reading from file", path
, 1);
546 clar__assert_equal(file
, func
, line
, "mismatched file length", 1, "%"PRIuZ
,
547 (size_t)expected_bytes
, (size_t)total_bytes
);
550 static char *_cl_restore_home
= NULL
;
552 void cl_fake_home_cleanup(void *payload
)
554 char *restore
= _cl_restore_home
;
555 _cl_restore_home
= NULL
;
560 cl_git_pass(git_libgit2_opts(
561 GIT_OPT_SET_SEARCH_PATH
, GIT_CONFIG_LEVEL_GLOBAL
, restore
));
566 void cl_fake_home(void)
568 git_buf path
= GIT_BUF_INIT
;
570 cl_git_pass(git_libgit2_opts(
571 GIT_OPT_GET_SEARCH_PATH
, GIT_CONFIG_LEVEL_GLOBAL
, &path
));
573 _cl_restore_home
= git_buf_detach(&path
);
574 cl_set_cleanup(cl_fake_home_cleanup
, NULL
);
576 if (!git_path_exists("home"))
577 cl_must_pass(p_mkdir("home", 0777));
578 cl_git_pass(git_path_prettify(&path
, "home", NULL
));
579 cl_git_pass(git_libgit2_opts(
580 GIT_OPT_SET_SEARCH_PATH
, GIT_CONFIG_LEVEL_GLOBAL
, path
.ptr
));
581 git_buf_dispose(&path
);
584 void cl_sandbox_set_search_path_defaults(void)
586 git_buf path
= GIT_BUF_INIT
;
588 git_buf_joinpath(&path
, clar_sandbox_path(), "__config");
590 if (!git_path_exists(path
.ptr
))
591 cl_must_pass(p_mkdir(path
.ptr
, 0777));
594 GIT_OPT_SET_SEARCH_PATH
, GIT_CONFIG_LEVEL_GLOBAL
, path
.ptr
);
596 GIT_OPT_SET_SEARCH_PATH
, GIT_CONFIG_LEVEL_XDG
, path
.ptr
);
598 GIT_OPT_SET_SEARCH_PATH
, GIT_CONFIG_LEVEL_SYSTEM
, path
.ptr
);
600 GIT_OPT_SET_SEARCH_PATH
, GIT_CONFIG_LEVEL_PROGRAMDATA
, path
.ptr
);
602 git_buf_dispose(&path
);
606 bool cl_sandbox_supports_8dot3(void)
608 git_buf longpath
= GIT_BUF_INIT
;
613 git_buf_joinpath(&longpath
, clar_sandbox_path(), "longer_than_8dot3"));
615 cl_git_write2file(longpath
.ptr
, "", 0, O_RDWR
|O_CREAT
, 0666);
616 shortname
= git_win32_path_8dot3_name(longpath
.ptr
);
618 supported
= (shortname
!= NULL
);
620 git__free(shortname
);
621 git_buf_dispose(&longpath
);